| /* |
| * Copyright (C) 2016 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.testtype.suite; |
| |
| import com.android.annotations.VisibleForTesting; |
| import com.android.ddmlib.Log.LogLevel; |
| import com.android.tradefed.build.BuildRetrievalError; |
| import com.android.tradefed.build.IBuildInfo; |
| import com.android.tradefed.build.IDeviceBuildInfo; |
| import com.android.tradefed.config.Configuration; |
| import com.android.tradefed.config.ConfigurationException; |
| import com.android.tradefed.config.DynamicRemoteFileResolver; |
| import com.android.tradefed.config.IConfiguration; |
| import com.android.tradefed.config.IConfigurationReceiver; |
| import com.android.tradefed.config.IDeviceConfiguration; |
| import com.android.tradefed.config.Option; |
| import com.android.tradefed.config.Option.Importance; |
| import com.android.tradefed.config.OptionCopier; |
| import com.android.tradefed.device.DeviceNotAvailableException; |
| import com.android.tradefed.device.DeviceProperties; |
| import com.android.tradefed.device.ITestDevice; |
| import com.android.tradefed.device.NullDevice; |
| import com.android.tradefed.device.StubDevice; |
| import com.android.tradefed.device.cloud.NestedRemoteDevice; |
| import com.android.tradefed.device.connection.AbstractConnection; |
| import com.android.tradefed.device.connection.AdbTcpConnection; |
| 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.error.HarnessRuntimeException; |
| import com.android.tradefed.error.IHarnessException; |
| import com.android.tradefed.invoker.IInvocationContext; |
| import com.android.tradefed.invoker.TestInformation; |
| import com.android.tradefed.invoker.logger.CurrentInvocation; |
| import com.android.tradefed.invoker.logger.CurrentInvocation.IsolationGrade; |
| import com.android.tradefed.invoker.logger.InvocationMetricLogger; |
| import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; |
| import com.android.tradefed.invoker.logger.TfObjectTracker; |
| import com.android.tradefed.invoker.shard.token.ITokenRequest; |
| import com.android.tradefed.invoker.shard.token.TokenProperty; |
| import com.android.tradefed.invoker.tracing.CloseableTraceScope; |
| import com.android.tradefed.log.ITestLogger; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; |
| import com.android.tradefed.postprocessor.IPostProcessor; |
| import com.android.tradefed.result.ByteArrayInputStreamSource; |
| import com.android.tradefed.result.FailureDescription; |
| 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.result.ResultForwarder; |
| import com.android.tradefed.result.error.DeviceErrorIdentifier; |
| import com.android.tradefed.result.error.InfraErrorIdentifier; |
| import com.android.tradefed.result.error.TestErrorIdentifier; |
| import com.android.tradefed.retry.IRetryDecision; |
| import com.android.tradefed.retry.RetryStrategy; |
| import com.android.tradefed.service.TradefedFeatureClient; |
| import com.android.tradefed.suite.checker.ISystemStatusChecker; |
| import com.android.tradefed.suite.checker.ISystemStatusCheckerReceiver; |
| import com.android.tradefed.suite.checker.StatusCheckerResult; |
| import com.android.tradefed.suite.checker.StatusCheckerResult.CheckStatus; |
| import com.android.tradefed.targetprep.ITargetPreparer; |
| import com.android.tradefed.testtype.Abi; |
| import com.android.tradefed.testtype.IAbi; |
| import com.android.tradefed.testtype.IBuildReceiver; |
| import com.android.tradefed.testtype.IDeviceTest; |
| import com.android.tradefed.testtype.IInvocationContextReceiver; |
| import com.android.tradefed.testtype.IRemoteTest; |
| import com.android.tradefed.testtype.IReportNotExecuted; |
| import com.android.tradefed.testtype.IRuntimeHintProvider; |
| import com.android.tradefed.testtype.IShardableTest; |
| import com.android.tradefed.testtype.ITestCollector; |
| import com.android.tradefed.util.AbiFormatter; |
| import com.android.tradefed.util.AbiUtils; |
| import com.android.tradefed.util.MultiMap; |
| import com.android.tradefed.util.StreamUtil; |
| import com.android.tradefed.util.TimeUtil; |
| |
| import com.google.common.collect.ImmutableSet; |
| import com.proto.tradefed.feature.FeatureResponse; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Abstract class used to run Test Suite. This class provide the base of how the Suite will be run. |
| * Each implementation can define the list of tests via the {@link #loadTests()} method. |
| */ |
| public abstract class ITestSuite |
| implements IRemoteTest, |
| IDeviceTest, |
| IBuildReceiver, |
| ISystemStatusCheckerReceiver, |
| IShardableTest, |
| ITestCollector, |
| IInvocationContextReceiver, |
| IRuntimeHintProvider, |
| IMetricCollectorReceiver, |
| IConfigurationReceiver, |
| IReportNotExecuted, |
| ITokenRequest, |
| ITestLoggerReceiver { |
| |
| public static final String MODULE_START_TIME = "MODULE_START_TIME"; |
| public static final String MODULE_END_TIME = "MODULE_END_TIME"; |
| |
| public static final String SKIP_SYSTEM_STATUS_CHECKER = "skip-system-status-check"; |
| public static final String RUNNER_WHITELIST = "runner-whitelist"; |
| public static final String PREPARER_WHITELIST = "preparer-whitelist"; |
| public static final String MODULE_CHECKER_PRE = "PreModuleChecker"; |
| public static final String MODULE_CHECKER_POST = "PostModuleChecker"; |
| public static final String ABI_OPTION = "abi"; |
| public static final String SKIP_HOST_ARCH_CHECK = "skip-host-arch-check"; |
| public static final String PRIMARY_ABI_RUN = "primary-abi-only"; |
| public static final String PARAMETER_KEY = "parameter"; |
| public static final String MAINLINE_PARAMETER_KEY = "mainline-param"; |
| public static final String ACTIVE_MAINLINE_PARAMETER_KEY = "active-mainline-parameter"; |
| public static final String TOKEN_KEY = "token"; |
| public static final String MODULE_METADATA_INCLUDE_FILTER = "module-metadata-include-filter"; |
| public static final String MODULE_METADATA_EXCLUDE_FILTER = "module-metadata-exclude-filter"; |
| public static final String RANDOM_SEED = "random-seed"; |
| public static final String SKIP_STAGING_ARTIFACTS = "skip-staging-artifacts"; |
| public static final String STAGE_MODULE_ARTIFACTS = "stage-module-artifacts"; |
| |
| private static final String PRODUCT_CPU_ABI_KEY = "ro.product.cpu.abi"; |
| |
| public static final String TEST_TYPE_KEY = "test-type"; |
| public static final String TEST_TYPE_VALUE_PERFORMANCE = "performance"; |
| |
| private static final Set<String> ALLOWED_PREPARERS_CONFIGS = |
| ImmutableSet.of("/suite/allowed-preparers.txt", "/suite/google-allowed-preparers.txt"); |
| |
| @Deprecated |
| @Option( |
| name = "bugreport-on-failure", |
| description = |
| "Take a bugreport on every test failure. Warning: This may require a lot" |
| + "of storage space of the machine running the tests.") |
| private boolean mBugReportOnFailure = false; |
| |
| @Deprecated |
| @Option( |
| name = "logcat-on-failure", |
| description = "Take a logcat snapshot on every test failure." |
| ) |
| private boolean mLogcatOnFailure = false; |
| |
| @Deprecated |
| @Option( |
| name = "logcat-on-failure-size", |
| description = |
| "The max number of logcat data in bytes to capture when --logcat-on-failure is" |
| + " on. Should be an amount that can comfortably fit in memory.") |
| private int mMaxLogcatBytes = 500 * 1024; // 500K |
| |
| @Deprecated |
| @Option( |
| name = "screenshot-on-failure", |
| description = "Take a screenshot on every test failure." |
| ) |
| private boolean mScreenshotOnFailure = false; |
| |
| @Deprecated |
| @Option(name = "reboot-on-failure", description = "Reboot the device after every test failure.") |
| private boolean mRebootOnFailure = false; |
| |
| // Options for suite runner behavior |
| @Option(name = "reboot-per-module", description = "Reboot the device before every module run.") |
| private boolean mRebootPerModule = false; |
| |
| @Option(name = "skip-all-system-status-check", |
| description = "Whether all system status check between modules should be skipped") |
| private boolean mSkipAllSystemStatusCheck = false; |
| |
| @Option( |
| name = SKIP_SYSTEM_STATUS_CHECKER, |
| description = |
| "Disable specific system status checkers." |
| + "Specify zero or more SystemStatusChecker as canonical class names. e.g. " |
| + "\"com.android.tradefed.suite.checker.KeyguardStatusChecker\" If not " |
| + "specified, all configured or whitelisted system status checkers will " |
| + "run." |
| ) |
| private Set<String> mSystemStatusCheckBlacklist = new HashSet<>(); |
| |
| @Option( |
| name = "report-system-checkers", |
| description = "Whether reporting system checkers as test or not." |
| ) |
| private boolean mReportSystemChecker = false; |
| |
| @Option( |
| name = "random-order", |
| description = "Whether randomizing the order of the modules to be ran or not." |
| ) |
| private boolean mRandomOrder = false; |
| |
| @Option( |
| name = RANDOM_SEED, |
| description = "Seed to randomize the order of the modules." |
| ) |
| private long mRandomSeed = -1; |
| |
| @Option( |
| name = "collect-tests-only", |
| description = |
| "Only invoke the suite to collect list of applicable test cases. All " |
| + "test run callbacks will be triggered, but test execution will not be " |
| + "actually carried out." |
| ) |
| private boolean mCollectTestsOnly = false; |
| |
| // Abi related options |
| @Option( |
| name = ABI_OPTION, |
| shortName = 'a', |
| description = "the abi to test. For example: 'arm64-v8a'.", |
| importance = Importance.IF_UNSET |
| ) |
| private String mAbiName = null; |
| |
| @Option( |
| name = SKIP_HOST_ARCH_CHECK, |
| description = "Whether host architecture check should be skipped." |
| ) |
| private boolean mSkipHostArchCheck = false; |
| |
| @Option( |
| name = PRIMARY_ABI_RUN, |
| description = |
| "Whether to run tests with only the device primary abi. " |
| + "This is overriden by the --abi option." |
| ) |
| private boolean mPrimaryAbiRun = false; |
| |
| @Option( |
| name = MODULE_METADATA_INCLUDE_FILTER, |
| description = |
| "Include modules for execution based on matching of metadata fields: for any of " |
| + "the specified filter name and value, if a module has a metadata field " |
| + "with the same name and value, it will be included. When both module " |
| + "inclusion and exclusion rules are applied, inclusion rules will be " |
| + "evaluated first. Using this together with test filter inclusion rules " |
| + "may result in no tests to execute if the rules don't overlap." |
| ) |
| private MultiMap<String, String> mModuleMetadataIncludeFilter = new MultiMap<>(); |
| |
| @Option( |
| name = MODULE_METADATA_EXCLUDE_FILTER, |
| description = |
| "Exclude modules for execution based on matching of metadata fields: for any of " |
| + "the specified filter name and value, if a module has a metadata field " |
| + "with the same name and value, it will be excluded. When both module " |
| + "inclusion and exclusion rules are applied, inclusion rules will be " |
| + "evaluated first." |
| ) |
| private MultiMap<String, String> mModuleMetadataExcludeFilter = new MultiMap<>(); |
| |
| @Option(name = RUNNER_WHITELIST, description = "Runner class(es) that are allowed to run.") |
| private Set<String> mAllowedRunners = new HashSet<>(); |
| |
| @Option( |
| name = PREPARER_WHITELIST, |
| description = |
| "Preparer class(es) that are allowed to run. This mostly usefeul for dry-runs." |
| ) |
| private Set<String> mAllowedPreparers = new HashSet<>(); |
| |
| @Option( |
| name = "enable-module-dynamic-download", |
| description = |
| "Whether or not to allow the downloading of dynamic @option files at module level." |
| ) |
| private boolean mEnableDynamicDownload = false; |
| |
| @Option( |
| name = "intra-module-sharding", |
| description = "Whether or not to allow intra-module sharding." |
| ) |
| private boolean mIntraModuleSharding = true; |
| |
| @Option( |
| name = "isolated-module", |
| description = "Whether or not to attempt the module isolation between modules" |
| ) |
| private boolean mIsolatedModule = false; |
| |
| @Option( |
| name = "recover-device-by-cvd", |
| description = |
| "Try to recover the device by cvd tool when the device is gone during test" |
| + " running.") |
| protected boolean mRecoverDeviceByCvd = false; |
| |
| /** @deprecated to be deleted when next version is deployed */ |
| @Deprecated |
| @Option( |
| name = "retry-strategy", |
| description = |
| "The retry strategy to be used when re-running some tests with " |
| + "--max-testcase-run-count" |
| ) |
| private RetryStrategy mRetryStrategy = RetryStrategy.NO_RETRY; |
| |
| // [Options relate to module retry and intra-module retry][ |
| @Option( |
| name = "merge-attempts", |
| description = "Whether or not to use the merge the results of the different attempts." |
| ) |
| private boolean mMergeAttempts = true; |
| // end [Options relate to module retry and intra-module retry] |
| |
| @Option( |
| name = "partial-download-via-feature", |
| description = "Feature flag to test partial download via feature service.") |
| private boolean mStageArtifactsViaFeature = true; |
| |
| @Option( |
| name = SKIP_STAGING_ARTIFACTS, |
| description = "Skip staging artifacts with remote-files if already staged.") |
| private boolean mSkipStagingArtifacts = false; |
| |
| @Option( |
| name = "multi-devices-modules", |
| description = "Running strategy for modules that require multiple devices.") |
| private MultiDeviceModuleStrategy mMultiDevicesStrategy = MultiDeviceModuleStrategy.EXCLUDE_ALL; |
| |
| @Option( |
| name = "use-snapshot-for-reset", |
| description = "Feature flag to use snapshot/restore instead of powerwash.") |
| private boolean mUseSnapshotForReset = false; |
| |
| @Option( |
| name = "use-snapshot-before-first-module", |
| description = |
| "Immediately restore the device after taking a snapshot. Ensures tests are" |
| + " consistently run within a restored VM.") |
| private boolean mUseSnapshotBeforeFirstModule = false; |
| |
| @Option(name = "stage-remote-file", description = "Whether to allow staging of remote files.") |
| private boolean mStageRemoteFile = true; |
| |
| public enum MultiDeviceModuleStrategy { |
| EXCLUDE_ALL, |
| RUN, |
| ONLY_MULTI_DEVICES |
| } |
| |
| private ITestDevice mDevice; |
| private IBuildInfo mBuildInfo; |
| private List<ISystemStatusChecker> mSystemStatusCheckers; |
| private IInvocationContext mContext; |
| private List<IMetricCollector> mMetricCollectors; |
| private IConfiguration mMainConfiguration; |
| private Set<IAbi> mAbis = new LinkedHashSet<>(); |
| |
| // Sharding attributes |
| private boolean mIsSharded = false; |
| private ModuleDefinition mDirectModule = null; |
| private boolean mShouldMakeDynamicModule = true; |
| |
| // Current modules to run, null if not started to run yet. |
| private List<ModuleDefinition> mRunModules = null; |
| private ModuleDefinition mModuleInProgress = null; |
| // Logger to be used to files. |
| private ITestLogger mCurrentLogger = null; |
| // Whether or not we are currently in split |
| private boolean mIsSplitting = false; |
| |
| private boolean mDisableAutoRetryTimeReporting = false; |
| |
| private DynamicRemoteFileResolver mDynamicResolver = new DynamicRemoteFileResolver(); |
| |
| @VisibleForTesting |
| void setDynamicResolver(DynamicRemoteFileResolver resolver) { |
| mDynamicResolver = resolver; |
| } |
| |
| @VisibleForTesting |
| public void setDirectModule(ModuleDefinition module) { |
| mDirectModule = module; |
| mIsSharded = true; |
| } |
| |
| /** |
| * Abstract method to load the tests configuration that will be run. Each tests is defined by a |
| * {@link IConfiguration} and a unique name under which it will report results. |
| */ |
| public abstract LinkedHashMap<String, IConfiguration> loadTests(); |
| |
| /** |
| * Return an instance of the class implementing {@link ITestSuite}. |
| */ |
| private ITestSuite createInstance() { |
| try { |
| return this.getClass().getDeclaredConstructor().newInstance(); |
| } catch (InstantiationException |
| | IllegalAccessException |
| | InvocationTargetException |
| | NoSuchMethodException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public File getTestsDir() throws FileNotFoundException { |
| IBuildInfo build = getBuildInfo(); |
| File testsDir = null; |
| if (build instanceof IDeviceBuildInfo) { |
| testsDir = ((IDeviceBuildInfo) build).getTestsDir(); |
| } |
| if (testsDir != null && testsDir.exists()) { |
| return testsDir; |
| } |
| // TODO: handle multi build? |
| throw new FileNotFoundException("Could not found a tests dir folder."); |
| } |
| |
| private LinkedHashMap<String, IConfiguration> loadAndFilter() { |
| LinkedHashMap<String, IConfiguration> runConfig = loadTests(); |
| if (runConfig.isEmpty()) { |
| CLog.i("No config were loaded. Nothing to run."); |
| return runConfig; |
| } |
| |
| Set<String> moduleNames = new HashSet<>(); |
| LinkedHashMap<String, IConfiguration> filteredConfig = new LinkedHashMap<>(); |
| for (Entry<String, IConfiguration> config : runConfig.entrySet()) { |
| if (!mModuleMetadataIncludeFilter.isEmpty() |
| || !mModuleMetadataExcludeFilter.isEmpty()) { |
| if (!filterByConfigMetadata( |
| config.getValue(), |
| mModuleMetadataIncludeFilter, |
| mModuleMetadataExcludeFilter)) { |
| // if the module config did not pass the metadata filters, it's excluded |
| // from execution. |
| continue; |
| } |
| } |
| if (!filterByRunnerType(config.getValue(), mAllowedRunners)) { |
| // if the module config did not pass the runner type filter, it's excluded from |
| // execution. |
| continue; |
| } |
| switch (mMultiDevicesStrategy) { |
| case EXCLUDE_ALL: |
| if (config.getValue().getDeviceConfig().size() > 1) { |
| // Exclude multi-devices configs |
| continue; |
| } |
| break; |
| case ONLY_MULTI_DEVICES: |
| if (config.getValue().getDeviceConfig().size() == 1) { |
| // Exclude single devices configs |
| continue; |
| } |
| break; |
| default: |
| break; |
| } |
| filterPreparers(config.getValue(), mAllowedPreparers); |
| |
| // Copy the CoverageOptions from the main configuration to the module configuration. |
| if (mMainConfiguration != null) { |
| config.getValue().setCoverageOptions(mMainConfiguration.getCoverageOptions()); |
| } |
| |
| filteredConfig.put(config.getKey(), config.getValue()); |
| moduleNames.add(config.getValue().getConfigurationDescription().getModuleName()); |
| } |
| |
| if (stageAtInvocationLevel()) { |
| stageTestArtifacts(mDevice, moduleNames); |
| } else { |
| CLog.d(SKIP_STAGING_ARTIFACTS + " is set. Skipping #stageTestArtifacts"); |
| } |
| |
| runConfig.clear(); |
| return filteredConfig; |
| } |
| |
| private boolean stageAtInvocationLevel() { |
| if (mBuildInfo != null) { |
| if (mSkipStagingArtifacts |
| || mBuildInfo.getBuildAttributes().get(SKIP_STAGING_ARTIFACTS) != null) { |
| return false; |
| } else { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean stageModuleLevel() { |
| if (mBuildInfo != null) { |
| return mBuildInfo.getBuildAttributes().get(STAGE_MODULE_ARTIFACTS) != null; |
| } |
| return false; |
| } |
| |
| /** Helper to download all artifacts for the given modules. */ |
| private void stageTestArtifacts(ITestDevice device, Set<String> modules) { |
| if (mBuildInfo.getRemoteFiles() == null || mBuildInfo.getRemoteFiles().isEmpty()) { |
| CLog.d("No remote build info, skipping stageTestArtifacts"); |
| return; |
| } |
| CLog.i(String.format("Start to stage test artifacts for %d modules.", modules.size())); |
| long startTime = System.currentTimeMillis(); |
| try (CloseableTraceScope ignored = |
| new CloseableTraceScope( |
| InvocationMetricKey.stage_suite_test_artifacts.toString())) { |
| // Include the file if its path contains a folder name matching any of the module. |
| String moduleRegex = |
| modules.stream() |
| .map(m -> String.format("/%s/", m)) |
| .collect(Collectors.joining("|")); |
| List<String> includeFilters = Arrays.asList(moduleRegex); |
| // Ignore config file as it's part of config and jar zip artifact that's staged already. |
| List<String> excludeFilters = Arrays.asList("[.]config$", "[.]jar$"); |
| if (mStageArtifactsViaFeature) { |
| try (TradefedFeatureClient client = new TradefedFeatureClient()) { |
| Map<String, String> args = new HashMap<>(); |
| String destination = getTestsDir().getAbsolutePath(); |
| // In *TS cases, download from root dir reference instead of tests dir |
| if (mBuildInfo.getBuildAttributes().containsKey("ROOT_DIR")) { |
| destination = mBuildInfo.getBuildAttributes().get("ROOT_DIR"); |
| } |
| CLog.d( |
| "downloading to destination: %s the following include_filters: %s", |
| destination, includeFilters); |
| args.put(ResolvePartialDownload.DESTINATION_DIR, destination); |
| args.put( |
| ResolvePartialDownload.INCLUDE_FILTERS, |
| String.join(";", includeFilters)); |
| args.put( |
| ResolvePartialDownload.EXCLUDE_FILTERS, |
| String.join(";", excludeFilters)); |
| // Pass the remote paths |
| String remotePaths = |
| mBuildInfo.getRemoteFiles().stream() |
| .map(p -> p.toString()) |
| .collect(Collectors.joining(";")); |
| args.put(ResolvePartialDownload.REMOTE_PATHS, remotePaths); |
| FeatureResponse rep = |
| client.triggerFeature( |
| ResolvePartialDownload.RESOLVE_PARTIAL_DOWNLOAD_FEATURE_NAME, |
| args); |
| if (rep.hasErrorInfo()) { |
| throw new HarnessRuntimeException( |
| rep.getErrorInfo().getErrorTrace(), |
| InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR); |
| } |
| } catch (FileNotFoundException e) { |
| throw new HarnessRuntimeException( |
| e.getMessage(), e, InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR); |
| } |
| } else { |
| mDynamicResolver.setDevice(device); |
| mDynamicResolver.addExtraArgs( |
| mMainConfiguration.getCommandOptions().getDynamicDownloadArgs()); |
| for (File remoteFile : mBuildInfo.getRemoteFiles()) { |
| try { |
| mDynamicResolver.resolvePartialDownloadZip( |
| getTestsDir(), |
| remoteFile.toString(), |
| includeFilters, |
| excludeFilters); |
| } catch (BuildRetrievalError | FileNotFoundException e) { |
| String message = |
| String.format( |
| "Failed to download partial zip from %s for modules: %s", |
| remoteFile, String.join(", ", modules)); |
| CLog.e(message); |
| CLog.e(e); |
| if (e instanceof IHarnessException) { |
| throw new HarnessRuntimeException(message, (IHarnessException) e); |
| } |
| throw new HarnessRuntimeException( |
| message, e, InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR); |
| } |
| } |
| } |
| } |
| long elapsedTime = System.currentTimeMillis() - startTime; |
| InvocationMetricLogger.addInvocationMetrics( |
| InvocationMetricKey.STAGE_TESTS_TIME, elapsedTime); |
| CLog.i( |
| String.format( |
| "Staging test artifacts for %d modules finished in %s.", |
| modules.size(), TimeUtil.formatElapsedTime(elapsedTime))); |
| } |
| |
| /** Helper that creates and returns the list of {@link ModuleDefinition} to be executed. */ |
| private List<ModuleDefinition> createExecutionList() { |
| List<ModuleDefinition> runModules = new ArrayList<>(); |
| if (mDirectModule != null) { |
| // If we are sharded and already know what to run then we just do it. |
| runModules.add(mDirectModule); |
| mDirectModule.setDevice(mDevice); |
| mDirectModule.setBuild(mBuildInfo); |
| return runModules; |
| } |
| try (CloseableTraceScope ignore = new CloseableTraceScope("suite:createExecutionList")) { |
| LinkedHashMap<String, IConfiguration> runConfig = loadAndFilter(); |
| if (runConfig.isEmpty()) { |
| CLog.i("No config were loaded. Nothing to run."); |
| return runModules; |
| } |
| |
| Map<String, List<ITargetPreparer>> suitePreparersPerDevice = |
| getAllowedPreparerPerDevice(mMainConfiguration); |
| |
| for (Entry<String, IConfiguration> config : runConfig.entrySet()) { |
| // Validate the configuration, it will throw if not valid. |
| ValidateSuiteConfigHelper.validateConfig(config.getValue()); |
| Map<String, List<ITargetPreparer>> preparersPerDevice = |
| getPreparerPerDevice(config.getValue()); |
| ModuleDefinition module = |
| new ModuleDefinition( |
| config.getKey(), |
| config.getValue().getTests(), |
| preparersPerDevice, |
| suitePreparersPerDevice, |
| config.getValue().getMultiTargetPreparers(), |
| config.getValue()); |
| if (mDisableAutoRetryTimeReporting) { |
| module.disableAutoRetryReportingTime(); |
| } |
| module.setDevice(mDevice); |
| module.setBuild(mBuildInfo); |
| runModules.add(module); |
| } |
| |
| /** Randomize all the modules to be ran if random-order is set and no sharding. */ |
| if (mRandomOrder) { |
| randomizeTestModules(runModules, mRandomSeed); |
| } |
| |
| CLog.logAndDisplay(LogLevel.DEBUG, "[Total Unique Modules = %s]", runModules.size()); |
| // Free the map once we are done with it. |
| runConfig = null; |
| return runModules; |
| } |
| } |
| |
| /** |
| * Helper method that handle randomizing the order of the modules. |
| * |
| * @param runModules The {@code List<ModuleDefinition>} of the test modules to be ran. |
| * @param randomSeed The {@code long} seed used to randomize the order of test modules, use the |
| * current time as seed if no specified seed provided. |
| */ |
| @VisibleForTesting |
| void randomizeTestModules(List<ModuleDefinition> runModules, long randomSeed) { |
| // Use current time as seed if no specified seed provided. |
| if (randomSeed == -1) { |
| randomSeed = System.currentTimeMillis(); |
| } |
| CLog.i("Randomizing all the modules with seed: %s", randomSeed); |
| Collections.shuffle(runModules, new Random(randomSeed)); |
| mBuildInfo.addBuildAttribute(RANDOM_SEED, String.valueOf(randomSeed)); |
| } |
| |
| private void checkClassLoad(Set<String> classes, String type) { |
| for (String c : classes) { |
| try { |
| Class.forName(c); |
| } catch (ClassNotFoundException e) { |
| ConfigurationException ex = |
| new ConfigurationException( |
| String.format( |
| "--%s must contains valid class, %s was not found", |
| type, c), |
| e); |
| throw new RuntimeException(ex); |
| } |
| } |
| } |
| |
| /** Create the mapping of device to its target_preparer. */ |
| private Map<String, List<ITargetPreparer>> getPreparerPerDevice(IConfiguration config) { |
| Map<String, List<ITargetPreparer>> res = new LinkedHashMap<>(); |
| for (IDeviceConfiguration holder : config.getDeviceConfig()) { |
| List<ITargetPreparer> preparers = new ArrayList<>(); |
| res.put(holder.getDeviceName(), preparers); |
| preparers.addAll(holder.getTargetPreparers()); |
| } |
| return res; |
| } |
| |
| /** Create the mapping of device to its target_preparer that's allowed to rerun. */ |
| private Map<String, List<ITargetPreparer>> getAllowedPreparerPerDevice(IConfiguration config) { |
| // For unittests, mMainConfiguration might not have been set. |
| if (config == null) { |
| return new LinkedHashMap<String, List<ITargetPreparer>>(); |
| } |
| // Read the list of allowed suite level target preparers from resource files. |
| Set<String> allowedSuitePreparers = new HashSet<>(); |
| for (String resource : ALLOWED_PREPARERS_CONFIGS) { |
| try (InputStream resStream = ITestSuite.class.getResourceAsStream(resource)) { |
| if (resStream == null) { |
| CLog.d("Resource not found for allowed preparers: %s", resource); |
| continue; |
| } |
| List<String> preparers = |
| Arrays.asList(StreamUtil.getStringFromStream(resStream).split("\n")); |
| allowedSuitePreparers.addAll(preparers); |
| } catch (IOException e) { |
| CLog.e(e); |
| } |
| } |
| |
| Map<String, List<ITargetPreparer>> res = new LinkedHashMap<>(); |
| for (IDeviceConfiguration holder : config.getDeviceConfig()) { |
| List<ITargetPreparer> preparers = new ArrayList<>(); |
| for (ITargetPreparer preparer : holder.getTargetPreparers()) { |
| if (allowedSuitePreparers.contains(preparer.getClass().getCanonicalName())) { |
| preparers.add(preparer); |
| } |
| } |
| res.put(holder.getDeviceName(), preparers); |
| } |
| return res; |
| } |
| |
| /** |
| * Opportunity to clean up all the things that were needed during the suites setup but are not |
| * required to run the tests. |
| */ |
| public void cleanUpSuiteSetup() { |
| // Empty by default. |
| } |
| |
| /** Generic run method for all test loaded from {@link #loadTests()}. */ |
| @Override |
| public final void run(TestInformation testInfo, ITestInvocationListener listener) |
| throws DeviceNotAvailableException { |
| mCurrentLogger = listener; |
| // Load and check the module checkers, runners and preparers in black and whitelist |
| checkClassLoad(mSystemStatusCheckBlacklist, SKIP_SYSTEM_STATUS_CHECKER); |
| checkClassLoad(mAllowedRunners, RUNNER_WHITELIST); |
| checkClassLoad(mAllowedPreparers, PREPARER_WHITELIST); |
| |
| mRunModules = createExecutionList(); |
| // Check if we have something to run. |
| if (mRunModules.isEmpty()) { |
| CLog.i("No tests to be run."); |
| return; |
| } |
| |
| // Allow checkers to log files for easier debugging. |
| for (ISystemStatusChecker checker : mSystemStatusCheckers) { |
| if (checker instanceof ITestLoggerReceiver) { |
| ((ITestLoggerReceiver) checker).setTestLogger(listener); |
| } |
| } |
| |
| /** Create the list of listeners applicable at the module level. */ |
| List<ITestInvocationListener> moduleListeners = createModuleListeners(); |
| |
| if (mUseSnapshotForReset) { |
| AbstractConnection connection = mDevice.getConnection(); |
| if (connection instanceof AdbTcpConnection) { |
| // Capture a snapshot once at the beginning of the suite |
| if (((AdbTcpConnection) connection).getSuiteSnapshots().containsKey(mDevice)) { |
| CLog.d("Suite snapshot already taken for '%s'", mDevice.getSerialNumber()); |
| } else { |
| ((AdbTcpConnection) connection) |
| .snapshotDevice(mDevice, mContext.getInvocationId()); |
| ((AdbTcpConnection) connection) |
| .getSuiteSnapshots() |
| .put(mDevice, mContext.getInvocationId()); |
| } |
| if (mUseSnapshotBeforeFirstModule) { |
| String snapshot = |
| ((AdbTcpConnection) connection).getSuiteSnapshots().get(mDevice); |
| ((AdbTcpConnection) connection).recoverVirtualDevice(mDevice, snapshot, null); |
| } |
| } |
| } |
| |
| // Only print the running log if we are going to run something. |
| if (mRunModules.get(0).hasTests()) { |
| CLog.logAndDisplay( |
| LogLevel.INFO, |
| "%s running %s modules: %s", |
| mDevice.getSerialNumber(), |
| mRunModules.size(), |
| mRunModules); |
| } |
| |
| /** Run all the module, make sure to reduce the list to release resources as we go. */ |
| try { |
| while (!mRunModules.isEmpty()) { |
| ModuleDefinition module = mRunModules.remove(0); |
| |
| if (!shouldModuleRun(module)) { |
| continue; |
| } |
| // Before running the module we ensure it has tests at this point or skip completely |
| // to avoid running SystemCheckers and preparation for nothing. |
| if (!module.hasTests()) { |
| continue; |
| } |
| |
| try (CloseableTraceScope ignore = new CloseableTraceScope(module.getId())) { |
| if (!stageAtInvocationLevel() && stageModuleLevel()) { |
| stageTestArtifacts( |
| mDevice, |
| ImmutableSet.of( |
| module.getModuleConfiguration() |
| .getConfigurationDescription() |
| .getModuleName())); |
| } |
| // Populate the module context with devices and builds |
| for (String deviceName : mContext.getDeviceConfigNames()) { |
| module.getModuleInvocationContext() |
| .addAllocatedDevice(deviceName, mContext.getDevice(deviceName)); |
| module.getModuleInvocationContext() |
| .addDeviceBuildInfo(deviceName, mContext.getBuildInfo(deviceName)); |
| } |
| // Add isolation status before module start for reporting |
| if (!IsolationGrade.NOT_ISOLATED.equals( |
| CurrentInvocation.moduleCurrentIsolation())) { |
| module.getModuleInvocationContext() |
| .addInvocationAttribute( |
| ModuleDefinition.MODULE_ISOLATED, |
| CurrentInvocation.moduleCurrentIsolation().toString()); |
| } |
| // Add module speicifc post processors. |
| listener = listenerWithPostProcessorsForPerfModule(module, listener); |
| // Only the module callback will be called here. |
| ITestInvocationListener listenerWithCollectors = listener; |
| if (mMetricCollectors != null) { |
| for (IMetricCollector collector : |
| CollectorHelper.cloneCollectors(mMetricCollectors)) { |
| if (collector.isDisabled()) { |
| CLog.d("%s has been disabled. Skipping.", collector); |
| } else if (!collector.captureModuleLevel()) { |
| CLog.d("%s isn't applicable at module level. Skipping.", collector); |
| } else { |
| if (collector instanceof IConfigurationReceiver) { |
| ((IConfigurationReceiver) collector) |
| .setConfiguration(module.getModuleConfiguration()); |
| } |
| try (CloseableTraceScope ignored = |
| new CloseableTraceScope( |
| "init_for_module_" |
| + collector.getClass().getSimpleName())) { |
| listenerWithCollectors = |
| collector.init( |
| module.getModuleInvocationContext(), |
| listenerWithCollectors); |
| TfObjectTracker.countWithParents(collector.getClass()); |
| } |
| } |
| } |
| } |
| module.getModuleInvocationContext() |
| .addInvocationAttribute( |
| MODULE_START_TIME, Long.toString(System.currentTimeMillis())); |
| listenerWithCollectors.testModuleStarted(module.getModuleInvocationContext()); |
| mModuleInProgress = module; |
| // Trigger module start on module level listener too |
| new ResultForwarder(moduleListeners) |
| .testModuleStarted(module.getModuleInvocationContext()); |
| TestInformation moduleInfo = |
| TestInformation.createModuleTestInfo( |
| testInfo, module.getModuleInvocationContext()); |
| logModuleConfig(listener, module); |
| try { |
| runSingleModule(module, moduleInfo, listener, moduleListeners); |
| } finally { |
| module.getModuleInvocationContext() |
| .addInvocationAttribute( |
| MODULE_END_TIME, Long.toString(System.currentTimeMillis())); |
| // Trigger module end on module level listener too |
| new ResultForwarder(moduleListeners).testModuleEnded(); |
| // clear out module invocation context since we are now done with module |
| // execution |
| listenerWithCollectors.testModuleEnded(); |
| mModuleInProgress = null; |
| // Following modules will not be isolated if no action is taken |
| CurrentInvocation.setModuleIsolation(IsolationGrade.NOT_ISOLATED); |
| } |
| // Module isolation routine |
| moduleIsolation(mContext, listener); |
| } |
| } |
| } catch (DeviceNotAvailableException e) { |
| CLog.e( |
| "A DeviceNotAvailableException occurred, following modules did not run: %s", |
| mRunModules); |
| reportNotExecuted(listener, "Module did not run due to device not available."); |
| throw e; |
| } |
| } |
| |
| /** |
| * Returns a listener with module-level post processors (for perf modules) instered to the |
| * listener chain. |
| */ |
| private ITestInvocationListener listenerWithPostProcessorsForPerfModule( |
| ModuleDefinition module, ITestInvocationListener listener) { |
| IConfiguration config = module.getModuleConfiguration(); |
| List<String> testTypes = config.getConfigurationDescription().getMetaData(TEST_TYPE_KEY); |
| if (testTypes == null || !testTypes.contains(TEST_TYPE_VALUE_PERFORMANCE)) { |
| return listener; // not a perf module |
| } |
| List<IPostProcessor> topLevelPostProcessors = mMainConfiguration.getPostProcessors(); |
| List<IPostProcessor> modulePostProcessors = config.getPostProcessors(); |
| if (modulePostProcessors.size() > 0 && topLevelPostProcessors.size() > 0) { |
| CLog.w("Post processors specified at both top level and module level (%s)", module); |
| } |
| for (IPostProcessor postProcessor : modulePostProcessors) { |
| try { |
| listener = postProcessor.init(listener); |
| } catch (Exception e) { |
| CLog.e( |
| "Post processor %s is ignored as it fails to init() with exception: %s", |
| postProcessor.getClass().getSimpleName(), e); |
| } |
| } |
| return listener; |
| } |
| |
| /** Log the module configuration. */ |
| private void logModuleConfig(ITestLogger logger, ModuleDefinition module) { |
| try (StringWriter configXmlWriter = new StringWriter(); |
| PrintWriter wrapperWriter = new PrintWriter(configXmlWriter)) { |
| module.getModuleConfiguration() |
| .dumpXml( |
| wrapperWriter, |
| new ArrayList<String>(Configuration.NON_MODULE_OBJECTS), |
| true, |
| false); |
| wrapperWriter.flush(); |
| // Specified UTF-8 encoding for an abundance of caution, but its possible we could want |
| // something else in the future |
| byte[] configXmlByteArray = configXmlWriter.toString().getBytes("UTF-8"); |
| try (InputStreamSource source = new ByteArrayInputStreamSource(configXmlByteArray)) { |
| logger.testLog("module-configuration", LogDataType.HARNESS_CONFIG, source); |
| } |
| } catch (RuntimeException | IOException e) { |
| CLog.e(e); |
| } |
| } |
| |
| /** |
| * Returns the list of {@link ITestInvocationListener} applicable to the {@link ModuleListener} |
| * level. These listeners will be re-used for each module, they will not be re-instantiated so |
| * they should not assume an internal state. |
| */ |
| protected List<ITestInvocationListener> createModuleListeners() { |
| return new ArrayList<>(); |
| } |
| |
| /** |
| * Routine that attempt to reset a device between modules in order to provide isolation. |
| * |
| * @param context The invocation context. |
| * @param logger A logger where extra logs can be saved. |
| * @throws DeviceNotAvailableException |
| */ |
| private void moduleIsolation(IInvocationContext context, ITestLogger logger) |
| throws DeviceNotAvailableException { |
| if (!mIsolatedModule) { |
| return; |
| } |
| // TODO: we can probably make it smarter: Did any test ran for example? |
| ITestDevice device = context.getDevices().get(0); |
| if (device instanceof NestedRemoteDevice) { |
| boolean res = ((NestedRemoteDevice) device).resetVirtualDevice(); |
| if (!res) { |
| String serial = device.getSerialNumber(); |
| throw new DeviceNotAvailableException( |
| String.format( |
| "Failed to reset the AVD '%s' during module isolation.", serial), |
| serial); |
| } |
| } else if (mUseSnapshotForReset) { |
| AbstractConnection connection = device.getConnection(); |
| if (connection instanceof AdbTcpConnection) { |
| String snapshot = ((AdbTcpConnection) connection).getSuiteSnapshots().get(device); |
| // snapshot should not be null, otherwise the device would have crashed. |
| ((AdbTcpConnection) connection).recoverVirtualDevice(device, snapshot, null); |
| } |
| } |
| } |
| |
| /** |
| * Helper method that handle running a single module logic. |
| * |
| * @param module The {@link ModuleDefinition} to be ran. |
| * @param moduleInfo The {@link TestInformation} for the module. |
| * @param listener The {@link ITestInvocationListener} where to report results |
| * @param moduleListeners The {@link ITestInvocationListener}s that runs at the module level. |
| * @param failureListener special listener that we add to collect information on failures. |
| * @throws DeviceNotAvailableException |
| */ |
| private void runSingleModule( |
| ModuleDefinition module, |
| TestInformation moduleInfo, |
| ITestInvocationListener listener, |
| List<ITestInvocationListener> moduleListeners) |
| throws DeviceNotAvailableException { |
| Map<String, String> properties = new LinkedHashMap<>(); |
| try (CloseableTraceScope ignored = new CloseableTraceScope("module_pre_check")) { |
| if (mRebootPerModule) { |
| if ("user".equals(mDevice.getProperty(DeviceProperties.BUILD_TYPE))) { |
| CLog.e( |
| "reboot-per-module should only be used during development, " |
| + "this is a\" user\" build device"); |
| } else { |
| CLog.d("Rebooting device before starting next module"); |
| mDevice.reboot(); |
| } |
| } |
| |
| if (!mSkipAllSystemStatusCheck && !mSystemStatusCheckers.isEmpty()) { |
| properties.putAll( |
| runPreModuleCheck( |
| module.getId(), mSystemStatusCheckers, mDevice, listener)); |
| } |
| if (mCollectTestsOnly) { |
| module.setCollectTestsOnly(mCollectTestsOnly); |
| } |
| if (mRecoverDeviceByCvd) { |
| module.setRecoverVirtualDevice(mRecoverDeviceByCvd); |
| } |
| // Pass the run defined collectors to be used. |
| module.setMetricCollectors(CollectorHelper.cloneCollectors(mMetricCollectors)); |
| // Pass the main invocation logSaver |
| module.setLogSaver(mMainConfiguration.getLogSaver()); |
| |
| IRetryDecision decision = mMainConfiguration.getRetryDecision(); |
| // Pass whether we should merge the attempts of not |
| if (mMergeAttempts |
| && decision.getMaxRetryCount() > 1 |
| && !RetryStrategy.NO_RETRY.equals(decision.getRetryStrategy())) { |
| CLog.d("Overriding '--merge-attempts' to false for auto-retry."); |
| mMergeAttempts = false; |
| } |
| module.setMergeAttemps(mMergeAttempts); |
| // Pass the retry decision to be used. |
| module.setRetryDecision(decision); |
| // Restore the config, as the setter might have override it with module config. |
| if (decision instanceof IConfigurationReceiver) { |
| ((IConfigurationReceiver) decision).setConfiguration(mMainConfiguration); |
| } |
| |
| module.setEnableDynamicDownload(mEnableDynamicDownload); |
| module.transferSuiteLevelOptions(mMainConfiguration); |
| } |
| // Actually run the module |
| module.run( |
| moduleInfo, |
| listener, |
| moduleListeners, |
| getConfiguration().getRetryDecision().getMaxRetryCount()); |
| |
| if (!mSkipAllSystemStatusCheck && !mSystemStatusCheckers.isEmpty()) { |
| try (CloseableTraceScope ignored = new CloseableTraceScope("module_post_check")) { |
| properties.putAll( |
| runPostModuleCheck( |
| module.getId(), mSystemStatusCheckers, mDevice, listener)); |
| } |
| } |
| for (Map.Entry<String, String> entry : properties.entrySet()) { |
| module.getModuleInvocationContext() |
| .addInvocationAttribute(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| /** |
| * Helper to run the System Status checkers preExecutionChecks defined for the test and log |
| * their failures. |
| */ |
| private Map<String, String> runPreModuleCheck( |
| String moduleName, |
| List<ISystemStatusChecker> checkers, |
| ITestDevice device, |
| ITestInvocationListener listener) |
| throws DeviceNotAvailableException { |
| long startTime = System.currentTimeMillis(); |
| CLog.i("Running system status checker before module execution: %s", moduleName); |
| Map<String, String> failures = new LinkedHashMap<>(); |
| Map<String, String> properties = new LinkedHashMap<>(); |
| boolean bugreportNeeded = false; |
| for (ISystemStatusChecker checker : checkers) { |
| // Track usage of the checker |
| TfObjectTracker.countWithParents(checker.getClass()); |
| // Check if the status checker should be skipped. |
| if (mSystemStatusCheckBlacklist.contains(checker.getClass().getName())) { |
| CLog.d( |
| "%s was skipped via %s", |
| checker.getClass().getName(), SKIP_SYSTEM_STATUS_CHECKER); |
| continue; |
| } |
| |
| StatusCheckerResult result = new StatusCheckerResult(CheckStatus.FAILED); |
| try { |
| result = checker.preExecutionCheck(device); |
| } catch (RuntimeException e) { |
| CLog.e(e); |
| // Catch RuntimeException to avoid leaking throws that go to the invocation. |
| result.setErrorMessage(e.getMessage()); |
| result.setBugreportNeeded(true); |
| } |
| properties.putAll(result.getModuleProperties()); |
| if (!CheckStatus.SUCCESS.equals(result.getStatus())) { |
| String errorMessage = |
| (result.getErrorMessage() == null) ? "" : result.getErrorMessage(); |
| failures.put(checker.getClass().getCanonicalName(), errorMessage); |
| bugreportNeeded = bugreportNeeded | result.isBugreportNeeded(); |
| CLog.w("System status checker [%s] failed.", checker.getClass().getCanonicalName()); |
| } |
| } |
| if (!failures.isEmpty()) { |
| CLog.w("There are failed system status checkers: %s", failures.toString()); |
| if (bugreportNeeded && !(device.getIDevice() instanceof StubDevice)) { |
| device.logBugreport( |
| String.format("bugreport-checker-pre-module-%s", moduleName), listener); |
| } |
| } |
| |
| // We report System checkers like tests. |
| reportModuleCheckerResult(MODULE_CHECKER_PRE, moduleName, failures, startTime, listener); |
| InvocationMetricLogger.addInvocationPairMetrics( |
| InvocationMetricKey.STATUS_CHECKER_PAIR, startTime, System.currentTimeMillis()); |
| return properties; |
| } |
| |
| /** |
| * Helper to run the System Status checkers postExecutionCheck defined for the test and log |
| * their failures. |
| */ |
| private Map<String, String> runPostModuleCheck( |
| String moduleName, |
| List<ISystemStatusChecker> checkers, |
| ITestDevice device, |
| ITestInvocationListener listener) |
| throws DeviceNotAvailableException { |
| long startTime = System.currentTimeMillis(); |
| CLog.i("Running system status checker after module execution: %s", moduleName); |
| Map<String, String> failures = new LinkedHashMap<>(); |
| Map<String, String> properties = new LinkedHashMap<>(); |
| boolean bugreportNeeded = false; |
| for (ISystemStatusChecker checker : checkers) { |
| // Check if the status checker should be skipped. |
| if (mSystemStatusCheckBlacklist.contains(checker.getClass().getName())) { |
| continue; |
| } |
| |
| StatusCheckerResult result = new StatusCheckerResult(CheckStatus.FAILED); |
| try { |
| result = checker.postExecutionCheck(device); |
| } catch (RuntimeException e) { |
| CLog.e(e); |
| // Catch RuntimeException to avoid leaking throws that go to the invocation. |
| result.setErrorMessage(e.getMessage()); |
| result.setBugreportNeeded(true); |
| } catch (DeviceNotAvailableException dnae) { |
| // Wrap the DNAE to provide a better error message |
| String message = |
| String.format( |
| "Device became unavailable after %s due to: %s", |
| moduleName, dnae.getMessage()); |
| DeviceNotAvailableException wrapper = |
| new DeviceNotAvailableException(message, dnae, dnae.getSerial()); |
| throw wrapper; |
| } |
| properties.putAll(result.getModuleProperties()); |
| if (!CheckStatus.SUCCESS.equals(result.getStatus())) { |
| String errorMessage = |
| (result.getErrorMessage() == null) ? "" : result.getErrorMessage(); |
| failures.put(checker.getClass().getCanonicalName(), errorMessage); |
| bugreportNeeded = bugreportNeeded | result.isBugreportNeeded(); |
| CLog.w("System status checker [%s] failed", checker.getClass().getCanonicalName()); |
| } |
| } |
| if (!failures.isEmpty()) { |
| CLog.w("There are failed system status checkers: %s", failures.toString()); |
| if (bugreportNeeded && !(device.getIDevice() instanceof StubDevice)) { |
| device.logBugreport( |
| String.format("bugreport-checker-post-module-%s", moduleName), listener); |
| } |
| } |
| |
| // We report System checkers like tests. |
| reportModuleCheckerResult(MODULE_CHECKER_POST, moduleName, failures, startTime, listener); |
| InvocationMetricLogger.addInvocationPairMetrics( |
| InvocationMetricKey.STATUS_CHECKER_PAIR, startTime, System.currentTimeMillis()); |
| return properties; |
| } |
| |
| /** Helper to report status checker results as test results. */ |
| private void reportModuleCheckerResult( |
| String identifier, |
| String moduleName, |
| Map<String, String> failures, |
| long startTime, |
| ITestInvocationListener listener) { |
| if (!mReportSystemChecker) { |
| // do not log here, otherwise it could be very verbose. |
| return; |
| } |
| // Avoid messing with the final test count by making them empty runs. |
| listener.testRunStarted(identifier + "_" + moduleName, 0, 0, System.currentTimeMillis()); |
| if (!failures.isEmpty()) { |
| FailureDescription description = |
| FailureDescription.create( |
| String.format("%s failed '%s' checkers", moduleName, failures)) |
| .setErrorIdentifier(TestErrorIdentifier.MODULE_CHANGED_SYSTEM_STATUS); |
| listener.testRunFailed(description); |
| } |
| listener.testRunEnded( |
| System.currentTimeMillis() - startTime, new HashMap<String, Metric>()); |
| } |
| |
| /** Returns true if we are currently in {@link #split(int)}. */ |
| public boolean isSplitting() { |
| return mIsSplitting; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Collection<IRemoteTest> split(Integer shardCountHint, TestInformation testInfo) { |
| if (shardCountHint == null || shardCountHint <= 1 || mIsSharded) { |
| // cannot shard or already sharded |
| return null; |
| } |
| // TODO: Replace by relying on testInfo directly |
| setBuild(testInfo.getBuildInfo()); |
| setDevice(testInfo.getDevice()); |
| setInvocationContext(testInfo.getContext()); |
| |
| mIsSplitting = true; |
| try { |
| LinkedHashMap<String, IConfiguration> runConfig = loadAndFilter(); |
| if (runConfig.isEmpty()) { |
| CLog.i("No config were loaded. Nothing to run."); |
| return null; |
| } |
| injectInfo(runConfig, testInfo); |
| |
| // We split individual tests on double the shardCountHint to provide better average. |
| // The test pool mechanism prevent this from creating too much overhead. |
| List<ModuleDefinition> splitModules = |
| ModuleSplitter.splitConfiguration( |
| testInfo, |
| runConfig, |
| getAllowedPreparerPerDevice(mMainConfiguration), |
| shardCountHint, |
| mShouldMakeDynamicModule, |
| mIntraModuleSharding); |
| runConfig.clear(); |
| runConfig = null; |
| |
| // Clean up the parent that will get sharded: It is fine to clean up before copying the |
| // options, because the sharded module is already created/populated so there is no need |
| // to carry these extra data. |
| cleanUpSuiteSetup(); |
| |
| // create an association of one ITestSuite <=> one ModuleDefinition as the smallest |
| // execution unit supported. |
| List<IRemoteTest> splitTests = new ArrayList<>(); |
| for (ModuleDefinition m : splitModules) { |
| ITestSuite suite = createInstance(); |
| OptionCopier.copyOptionsNoThrow(this, suite); |
| suite.mIsSharded = true; |
| suite.mDirectModule = m; |
| splitTests.add(suite); |
| } |
| // return the list of ITestSuite with their ModuleDefinition assigned |
| return splitTests; |
| } finally { |
| // Done splitting at that point |
| mIsSplitting = false; |
| } |
| } |
| |
| /** |
| * Inject {@link ITestDevice} and {@link IBuildInfo} to the {@link IRemoteTest}s in the config |
| * before sharding since they may be needed. |
| */ |
| private void injectInfo( |
| LinkedHashMap<String, IConfiguration> runConfig, TestInformation testInfo) { |
| for (IConfiguration config : runConfig.values()) { |
| for (IRemoteTest test : config.getTests()) { |
| if (test instanceof IBuildReceiver) { |
| ((IBuildReceiver) test).setBuild(testInfo.getBuildInfo()); |
| } |
| if (test instanceof IDeviceTest) { |
| ((IDeviceTest) test).setDevice(testInfo.getDevice()); |
| } |
| if (test instanceof IInvocationContextReceiver) { |
| ((IInvocationContextReceiver) test).setInvocationContext(testInfo.getContext()); |
| } |
| if (test instanceof ITestCollector) { |
| ((ITestCollector) test).setCollectTestsOnly(mCollectTestsOnly); |
| } |
| } |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void setDevice(ITestDevice device) { |
| mDevice = device; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ITestDevice getDevice() { |
| return mDevice; |
| } |
| |
| /** Set the value of mAbiName */ |
| public void setAbiName(String abiName) { |
| mAbiName = abiName; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setBuild(IBuildInfo buildInfo) { |
| mBuildInfo = buildInfo; |
| mBuildInfo.allowStagingRemoteFile(mStageRemoteFile); |
| } |
| |
| /** |
| * Implementation of {@link ITestSuite} may require the build info to load the tests. |
| */ |
| public IBuildInfo getBuildInfo() { |
| return mBuildInfo; |
| } |
| |
| /** Set the value of mPrimaryAbiRun */ |
| public void setPrimaryAbiRun(boolean primaryAbiRun) { |
| mPrimaryAbiRun = primaryAbiRun; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setSystemStatusChecker(List<ISystemStatusChecker> systemCheckers) { |
| mSystemStatusCheckers = systemCheckers; |
| } |
| |
| /** |
| * Run the test suite in collector only mode, this requires all the sub-tests to implements this |
| * interface too. |
| */ |
| @Override |
| public void setCollectTestsOnly(boolean shouldCollectTest) { |
| mCollectTestsOnly = shouldCollectTest; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void setMetricCollectors(List<IMetricCollector> collectors) { |
| mMetricCollectors = collectors; |
| } |
| |
| /** |
| * When doing distributed sharding, we cannot have ModuleDefinition that shares tests in a pool |
| * otherwise intra-module sharding will not work, so we allow to disable it. |
| */ |
| public void setShouldMakeDynamicModule(boolean dynamicModule) { |
| mShouldMakeDynamicModule = dynamicModule; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void setInvocationContext(IInvocationContext invocationContext) { |
| mContext = invocationContext; |
| } |
| |
| /** |
| * Returns the invocation context. |
| */ |
| public IInvocationContext getInvocationContext() { |
| return mContext; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void setTestLogger(ITestLogger testLogger) { |
| mCurrentLogger = testLogger; |
| } |
| |
| public ITestLogger getCurrentTestLogger() { |
| return mCurrentLogger; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public long getRuntimeHint() { |
| if (mDirectModule != null) { |
| CLog.d( |
| " %s: %s", |
| mDirectModule.getId(), |
| TimeUtil.formatElapsedTime(mDirectModule.getRuntimeHint())); |
| return mDirectModule.getRuntimeHint(); |
| } |
| return 0L; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void setConfiguration(IConfiguration configuration) { |
| mMainConfiguration = configuration; |
| } |
| |
| /** Returns the invocation {@link IConfiguration}. */ |
| public final IConfiguration getConfiguration() { |
| return mMainConfiguration; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void reportNotExecuted(ITestInvocationListener listener) { |
| reportNotExecuted(listener, IReportNotExecuted.NOT_EXECUTED_FAILURE); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void reportNotExecuted(ITestInvocationListener listener, String message) { |
| // If the runner is already in progress, report the remaining tests as not executed. |
| List<ModuleDefinition> runModules = null; |
| if (mRunModules != null) { |
| runModules = new ArrayList<>(mRunModules); |
| } |
| if (runModules == null) { |
| runModules = createExecutionList(); |
| } |
| |
| if (mModuleInProgress != null) { |
| // TODO: Ensure in-progress data make sense |
| String inProgressMessage = |
| String.format( |
| "Module %s was interrupted after starting. Results might not be " |
| + "accurate or complete.", |
| mModuleInProgress.getId()); |
| mModuleInProgress.reportNotExecuted(listener, inProgressMessage); |
| } |
| |
| while (!runModules.isEmpty()) { |
| ModuleDefinition module = runModules.remove(0); |
| module.reportNotExecuted(listener, message); |
| } |
| } |
| |
| public MultiMap<String, String> getModuleMetadataIncludeFilters() { |
| return mModuleMetadataIncludeFilter; |
| } |
| |
| public void addModuleMetadataIncludeFilters(MultiMap<String, String> filters) { |
| mModuleMetadataIncludeFilter.putAll(filters); |
| } |
| |
| public void addModuleMetadataExcludeFilters(MultiMap<String, String> filters) { |
| mModuleMetadataExcludeFilter.putAll(filters); |
| } |
| |
| /** |
| * Returns the {@link ModuleDefinition} to be executed directly, or null if none yet (when the |
| * ITestSuite has not been sharded yet). |
| */ |
| public ModuleDefinition getDirectModule() { |
| return mDirectModule; |
| } |
| |
| @Override |
| public Set<TokenProperty> getRequiredTokens(TestInformation testInfo) { |
| if (mDirectModule == null) { |
| return null; |
| } |
| return mDirectModule.getRequiredTokens(testInfo); |
| } |
| |
| /** |
| * Gets the set of ABIs supported by both Compatibility testing {@link |
| * AbiUtils#getAbisSupportedByCompatibility()} and the device under test. |
| * |
| * @return The set of ABIs to run the tests on |
| * @throws DeviceNotAvailableException |
| */ |
| public Set<IAbi> getAbis(ITestDevice device) throws DeviceNotAvailableException { |
| if (!mAbis.isEmpty()) { |
| return mAbis; |
| } |
| Set<IAbi> abis = new LinkedHashSet<>(); |
| Set<String> archAbis = getAbisForBuildTargetArch(); |
| // Handle null-device: use abi in common with host and suite build |
| if (mPrimaryAbiRun) { |
| if (mAbiName == null) { |
| // Get the primary from the device and make it the --abi to run. |
| mAbiName = getPrimaryAbi(device); |
| } else { |
| CLog.d( |
| "Option --%s supersedes the option --%s, using abi: %s", |
| ABI_OPTION, PRIMARY_ABI_RUN, mAbiName); |
| } |
| } |
| if (mAbiName != null) { |
| // A particular abi was requested, it still needs to be supported by the build. |
| if ((!mSkipHostArchCheck && !archAbis.contains(mAbiName)) |
| || !AbiUtils.isAbiSupportedByCompatibility(mAbiName)) { |
| throw new IllegalArgumentException( |
| String.format( |
| "Your tests suite hasn't been built with " |
| + "abi '%s' support, this suite currently supports '%s'.", |
| mAbiName, archAbis)); |
| } else { |
| abis.add(new Abi(mAbiName, AbiUtils.getBitness(mAbiName))); |
| return abis; |
| } |
| } else { |
| // Run on all abi in common between the device and suite builds. |
| List<String> deviceAbis = getDeviceAbis(device); |
| if (deviceAbis.isEmpty()) { |
| throw new HarnessRuntimeException( |
| String.format( |
| "Couldn't determinate the abi of the device '%s'.", |
| device.getSerialNumber()), |
| DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE); |
| } |
| for (String abi : deviceAbis) { |
| if ((mSkipHostArchCheck || archAbis.contains(abi)) |
| && AbiUtils.isAbiSupportedByCompatibility(abi)) { |
| abis.add(new Abi(abi, AbiUtils.getBitness(abi))); |
| } else { |
| CLog.d( |
| "abi '%s' is supported by device but not by this suite build (%s), " |
| + "tests will not run against it.", |
| abi, archAbis); |
| } |
| } |
| if (abis.isEmpty()) { |
| throw new IllegalArgumentException( |
| String.format( |
| "None of the abi supported by this tests suite build ('%s') are " |
| + "supported by the device ('%s').", |
| archAbis, deviceAbis)); |
| } |
| return abis; |
| } |
| } |
| |
| /** Returns the primary abi of the device or host if it's a null device. */ |
| private String getPrimaryAbi(ITestDevice device) throws DeviceNotAvailableException { |
| if (device.getIDevice() instanceof NullDevice) { |
| Set<String> hostAbis = getHostAbis(); |
| return hostAbis.iterator().next(); |
| } |
| String property = device.getProperty(PRODUCT_CPU_ABI_KEY); |
| if (property == null) { |
| String serial = device.getSerialNumber(); |
| throw new DeviceNotAvailableException( |
| String.format( |
| "Device '%s' was not online to query %s", serial, PRODUCT_CPU_ABI_KEY), |
| serial, |
| DeviceErrorIdentifier.DEVICE_UNAVAILABLE); |
| } |
| return property.trim(); |
| } |
| |
| /** Returns the list of abis supported by the device or host if it's a null device. */ |
| private List<String> getDeviceAbis(ITestDevice device) throws DeviceNotAvailableException { |
| if (device.getIDevice() instanceof NullDevice) { |
| return new ArrayList<>(getHostAbis()); |
| } |
| // Make it an arrayList to be able to modify the content. |
| return new ArrayList<>(Arrays.asList(AbiFormatter.getSupportedAbis(device, ""))); |
| } |
| |
| /** Return the abis supported by the Host build target architecture. Exposed for testing. */ |
| @VisibleForTesting |
| protected Set<String> getAbisForBuildTargetArch() { |
| return getAbisForBuildTargetArchFromSuite(); |
| } |
| |
| /** Returns the possible abis from the TestSuiteInfo. */ |
| public static Set<String> getAbisForBuildTargetArchFromSuite() { |
| // If TestSuiteInfo does not exists, the stub arch will be replaced by all possible abis. |
| Set<String> abis = new LinkedHashSet<>(); |
| for (String arch : TestSuiteInfo.getInstance().getTargetArchs()) { |
| abis.addAll(AbiUtils.getAbisForArch(arch)); |
| } |
| return abis; |
| } |
| |
| /** Returns the host machine abis. */ |
| @VisibleForTesting |
| protected Set<String> getHostAbis() { |
| return AbiUtils.getHostAbi(); |
| } |
| |
| /** Returns the abi requested with the option -a or --abi. */ |
| public final String getRequestedAbi() { |
| return mAbiName; |
| } |
| |
| /** |
| * Apply the metadata filter to the config and see if the config should run. |
| * |
| * @param config The {@link IConfiguration} being evaluated. |
| * @param include the metadata include filter |
| * @param exclude the metadata exclude filter |
| * @return True if the module should run, false otherwise. |
| */ |
| @VisibleForTesting |
| public boolean filterByConfigMetadata( |
| IConfiguration config, |
| MultiMap<String, String> include, |
| MultiMap<String, String> exclude) { |
| MultiMap<String, String> metadata = config.getConfigurationDescription().getAllMetaData(); |
| boolean shouldInclude = false; |
| for (String key : include.keySet()) { |
| Set<String> filters = new HashSet<>(include.get(key)); |
| if (metadata.containsKey(key)) { |
| filters.retainAll(metadata.get(key)); |
| if (!filters.isEmpty()) { |
| // inclusion filter is not empty and there's at least one matching inclusion |
| // rule so there's no need to match other inclusion rules |
| shouldInclude = true; |
| break; |
| } |
| } |
| } |
| if (!include.isEmpty() && !shouldInclude) { |
| // if inclusion filter is not empty and we didn't find a match, the module will not be |
| // included |
| return false; |
| } |
| // Now evaluate exclusion rules, this ordering also means that exclusion rules may override |
| // inclusion rules: a config already matched for inclusion may still be excluded if matching |
| // rules exist |
| for (String key : exclude.keySet()) { |
| Set<String> filters = new HashSet<>(exclude.get(key)); |
| if (metadata.containsKey(key)) { |
| filters.retainAll(metadata.get(key)); |
| if (!filters.isEmpty()) { |
| // we found at least one matching exclusion rules, so we are excluding this |
| // this module |
| return false; |
| } |
| } |
| } |
| // we've matched at least one inclusion rule (if there's any) AND we didn't match any of the |
| // exclusion rules (if there's any) |
| return true; |
| } |
| |
| /** |
| * Filter out the preparers that were not whitelisted. This is useful for collect-tests-only |
| * where some preparers are not needed to dry run through the invocation. |
| * |
| * @param config the {@link IConfiguration} considered for filtering. |
| * @param preparerWhiteList the current preparer whitelist. |
| */ |
| @VisibleForTesting |
| void filterPreparers(IConfiguration config, Set<String> preparerWhiteList) { |
| // If no filters was provided, skip the filtering. |
| if (preparerWhiteList.isEmpty()) { |
| return; |
| } |
| for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) { |
| List<ITargetPreparer> preparers = new ArrayList<>(deviceConfig.getTargetPreparers()); |
| for (ITargetPreparer prep : preparers) { |
| if (!preparerWhiteList.contains(prep.getClass().getName())) { |
| deviceConfig.getTargetPreparers().remove(prep); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Apply the Runner whitelist filtering, removing any runner that was not whitelisted. If a |
| * configuration has several runners, some might be removed and the config will still run. |
| * |
| * @param config The {@link IConfiguration} being evaluated. |
| * @param allowedRunners The current runner whitelist. |
| * @return True if the configuration module is allowed to run, false otherwise. |
| */ |
| @VisibleForTesting |
| protected boolean filterByRunnerType(IConfiguration config, Set<String> allowedRunners) { |
| // If no filters are provided, simply run everything. |
| if (allowedRunners.isEmpty()) { |
| return true; |
| } |
| Iterator<IRemoteTest> iterator = config.getTests().iterator(); |
| while (iterator.hasNext()) { |
| IRemoteTest test = iterator.next(); |
| if (!allowedRunners.contains(test.getClass().getName())) { |
| CLog.d( |
| "Runner '%s' in module '%s' was skipped by the runner whitelist: '%s'.", |
| test.getClass().getName(), config.getName(), allowedRunners); |
| iterator.remove(); |
| } |
| } |
| |
| if (config.getTests().isEmpty()) { |
| CLog.d("Module %s does not have any more tests, skipping it.", config.getName()); |
| return false; |
| } |
| return true; |
| } |
| |
| void disableAutoRetryTimeReporting() { |
| mDisableAutoRetryTimeReporting = true; |
| } |
| |
| @VisibleForTesting |
| void setModuleInProgress(ModuleDefinition moduleInProgress) { |
| mModuleInProgress = moduleInProgress; |
| } |
| |
| public final void setAbis(Set<IAbi> abis) { |
| mAbis.addAll(abis); |
| } |
| |
| protected boolean shouldModuleRun(ModuleDefinition module) { |
| return true; |
| } |
| |
| public void setMultiDeviceStrategy(MultiDeviceModuleStrategy strategy) { |
| mMultiDevicesStrategy = strategy; |
| } |
| |
| public MultiDeviceModuleStrategy getMultiDeviceStrategy() { |
| return mMultiDevicesStrategy; |
| } |
| |
| public void setIntraModuleSharding(boolean intraModuleSharding) { |
| mIntraModuleSharding = intraModuleSharding; |
| } |
| |
| public boolean getIntraModuleSharding() { |
| return mIntraModuleSharding; |
| } |
| } |