Merge "Snap for 6877830 from 93374d31f2ad53f028c2614f492d429e93010691 to sdk-release" into sdk-release
diff --git a/Android.bp b/Android.bp
index 4d6f01b..fb8090f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -48,6 +48,8 @@
"-Xep:TryFailThrowable:ERROR",
"-Xep:UnnecessaryParentheses:ERROR",
"-Xep:UseCorrectAssertInTests:ERROR",
+ "-XepDisableWarningsInGeneratedCode", // Disable warnings in gRPC generated code.
+ "-XepExcludedPaths:.*/srcjars/.*"
],
},
}
@@ -77,6 +79,24 @@
],
}
+java_genrule_host {
+ name: "lab-resource-grpc-gen",
+ srcs: [
+ "proto/monitoring/server/lab_resource.proto",
+ ],
+ tools: [
+ "aprotoc",
+ "protoc-gen-grpc-java-plugin",
+ "soong_zip",
+ ],
+ arch: "common",
+ cmd: "$(location aprotoc) -Iexternal/protobuf/src" +
+ " -Itools/tradefederation/core/proto/monitoring/server" +
+ " --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-java-plugin) $(in)" +
+ " --grpc_out=$(genDir) && $(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
+ out: ["tradefed-grpc.srcjar"],
+}
+
python_library_host {
name: "tradefed-protos-py",
pkg_path: "atest",
@@ -133,6 +153,7 @@
srcs: [
"src/**/*.java",
"global_configuration/**/*.java",
+ ":lab-resource-grpc-gen",
],
static_libs: [
"tradefed-common-util",
@@ -159,6 +180,14 @@
"tradefed-protos",
"tradefed-isolation-protos",
"tradefed-lite",
+ "guava",
+ "guava-testlib",
+ "grpc-java",
+ "grpc-java-testing",
+ "grpc-java-netty-shaded",
+ "javax-annotation-api-prebuilt-host-jar",
+ "opencensus-java-api",
+ "opencensus-java-contrib-grpc-metrics",
],
libs: [
"loganalysis",
@@ -210,7 +239,6 @@
"-werror " +
"-package " +
"-devsite ",
- create_stubs: false,
}
sh_binary_host {
diff --git a/README.md b/README.md
index 201d82b..38d8674 100644
--- a/README.md
+++ b/README.md
@@ -7,11 +7,21 @@
Other test harnesses like Compatibility Test Suite (CTS) and Vendor Test Suite
(VTS) use TF as a basis and extend it for their particular needs.
-Building TF:
+### Building TF:
+
* source build/envsetup.sh
* tapas tradefed-all
* make -j8
+### Getting Code Reviewed
+
+ 1. Create your change in Gerrit
+ 2. Add the reviewer named "Tradefed Codereview" (email: tradefed-codereview@tradefederation.google.com.iam.gserviceaccount.com)
+ 3. Review the code review guidance at go/tf-guidelines and go/tradefed-code-reviews
+ 4. GWSQ should add a couple of people from the team to review your code and give feedback.
+
+### More information
+
More information at:
https://source.android.com/devices/tech/test_infra/tradefed/
diff --git a/atest/Android.bp b/atest/Android.bp
index 53553fd..a403173 100644
--- a/atest/Android.bp
+++ b/atest/Android.bp
@@ -134,7 +134,6 @@
"atest_proto",
],
test_config: "atest_unittests.xml",
- test_suites: ["general-tests"],
defaults: ["atest_py2_default"],
}
diff --git a/atest/TEST_MAPPING b/atest/TEST_MAPPING
index 32e6a6d..09a0ffb 100644
--- a/atest/TEST_MAPPING
+++ b/atest/TEST_MAPPING
@@ -28,11 +28,11 @@
}
],
"presubmit": [
- {
- // Host side ATest unittests.
- "name": "atest_unittests",
- "host": true
- },
+// {
+// // Host side ATest unittests.
+// "name": "atest_unittests",
+// "host": true
+// },
{
// Host side metrics tests.
"name": "asuite_metrics_lib_tests",
diff --git a/atest/asuite_metrics.py b/atest/asuite_metrics.py
index 8dcd7dc..88fca0a 100644
--- a/atest/asuite_metrics.py
+++ b/atest/asuite_metrics.py
@@ -36,17 +36,17 @@
'.config', 'asuite', '.metadata')
_ANDROID_BUILD_TOP = 'ANDROID_BUILD_TOP'
-DUMMY_UUID = '00000000-0000-4000-8000-000000000000'
+UNUSED_UUID = '00000000-0000-4000-8000-000000000000'
#pylint: disable=broad-except
-def log_event(metrics_url, dummy_key_fallback=True, **kwargs):
+def log_event(metrics_url, unused_key_fallback=True, **kwargs):
"""Base log event function for asuite backend.
Args:
metrics_url: String, URL to report metrics to.
- dummy_key_fallback: Boolean, If True and unable to get grouping key,
- use a dummy key otherwise return out. Sometimes we
+ unused_key_fallback: Boolean, If True and unable to get grouping key,
+ use a unused key otherwise return out. Sometimes we
don't want to return metrics for users we are
unable to identify. Default True.
kwargs: Dict, additional fields we want to return metrics for.
@@ -55,9 +55,9 @@
try:
key = str(_get_grouping_key())
except Exception:
- if not dummy_key_fallback:
+ if not unused_key_fallback:
return
- key = DUMMY_UUID
+ key = UNUSED_UUID
data = {'grouping_key': key,
'run_id': str(uuid.uuid4())}
if kwargs:
diff --git a/atest/metrics/metrics_base.py b/atest/metrics/metrics_base.py
index 3d5abe8..44b3819 100644
--- a/atest/metrics/metrics_base.py
+++ b/atest/metrics/metrics_base.py
@@ -92,7 +92,7 @@
_user_key = str(asuite_metrics._get_grouping_key())
#pylint: disable=broad-except
except Exception:
- _user_key = asuite_metrics.DUMMY_UUID
+ _user_key = asuite_metrics.UNUSED_UUID
_user_type = get_user_type()
_log_source = ATEST_LOG_SOURCE[_user_type]
cc = clearcut_client.Clearcut(_log_source)
diff --git a/atest/test_runner_handler.py b/atest/test_runner_handler.py
index 86f42cb..3c18119 100644
--- a/atest/test_runner_handler.py
+++ b/atest/test_runner_handler.py
@@ -94,11 +94,11 @@
Returns:
Set of build targets required by the test runners.
"""
- dummy_result_dir = ''
+ unused_result_dir = ''
test_runner_build_req = set()
for test_runner, _ in group_tests_by_test_runners(test_infos):
test_runner_build_req |= test_runner(
- dummy_result_dir,
+ unused_result_dir,
module_info=module_info).get_test_runner_build_reqs()
return test_runner_build_req
diff --git a/atest/unittest_data/path_testing/PathTesting.java b/atest/unittest_data/path_testing/PathTesting.java
index 468307a..2245c67 100644
--- a/atest/unittest_data/path_testing/PathTesting.java
+++ b/atest/unittest_data/path_testing/PathTesting.java
@@ -16,7 +16,7 @@
package android.jank.cts.ui;
-/** Dummy Class file for unit tests. */
+/** UNUSED Class file for unit tests. */
public class SomeClassForTesting {
- private static final String SOME_DUMMY_VAR = "For testing purposes";
+ private static final String SOME_UNUSED_VAR = "For testing purposes";
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationSuiteResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationSuiteResultReporter.java
deleted file mode 100644
index c551496..0000000
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationSuiteResultReporter.java
+++ /dev/null
@@ -1,597 +0,0 @@
-/*
- * Copyright (C) 2018 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.compatibility.common.tradefed.result.suite;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.util.DeviceInfo;
-import com.android.compatibility.common.util.ResultHandler;
-import com.android.compatibility.common.util.ResultUploader;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.IConfiguration;
-import com.android.tradefed.config.IConfigurationReceiver;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.config.OptionClass;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.ILogSaver;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.ITestSummaryListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.result.LogFile;
-import com.android.tradefed.result.LogFileSaver;
-import com.android.tradefed.result.TestRunResult;
-import com.android.tradefed.result.TestSummary;
-import com.android.tradefed.result.suite.IFormatterGenerator;
-import com.android.tradefed.result.suite.SuiteResultReporter;
-import com.android.tradefed.result.suite.XmlFormattedGeneratorReporter;
-import com.android.tradefed.util.FileUtil;
-import com.android.tradefed.util.StreamUtil;
-import com.android.tradefed.util.ZipUtil;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.stream.StreamResult;
-import javax.xml.transform.stream.StreamSource;
-
-/**
- * Extension of {@link XmlFormattedGeneratorReporter} and {@link SuiteResultReporter} to handle
- * Compatibility specific format and operations.
- */
-@OptionClass(alias = "result-reporter")
-public class CertificationSuiteResultReporter extends XmlFormattedGeneratorReporter
- implements IConfigurationReceiver, ITestSummaryListener {
-
- public static final String LATEST_LINK_NAME = "latest";
- public static final String SUMMARY_FILE = "invocation_summary.txt";
- public static final String HTLM_REPORT_NAME = "test_result.html";
- public static final String REPORT_XSL_FILE_NAME = "compatibility_result.xsl";
- public static final String FAILURE_REPORT_NAME = "test_result_failures_suite.html";
- public static final String FAILURE_XSL_FILE_NAME = "compatibility_failures.xsl";
-
- public static final String BUILD_FINGERPRINT = "build_fingerprint";
-
- @Option(name = "result-server", description = "Server to publish test results.")
- private String mResultServer;
-
- @Option(
- name = "disable-result-posting",
- description ="Disable result posting into report server."
- )
- private boolean mDisableResultPosting = false;
-
- @Option(name = "include-test-log-tags", description = "Include test log tags in report.")
- private boolean mIncludeTestLogTags = false;
-
- @Option(name = "use-log-saver", description = "Also saves generated result with log saver")
- private boolean mUseLogSaver = false;
-
- @Option(name = "compress-logs", description = "Whether logs will be saved with compression")
- private boolean mCompressLogs = true;
-
- public static final String INCLUDE_HTML_IN_ZIP = "html-in-zip";
- @Option(name = INCLUDE_HTML_IN_ZIP,
- description = "Whether failure summary report is included in the zip fie.")
- private boolean mIncludeHtml = false;
-
- private CompatibilityBuildHelper mBuildHelper;
-
- /** The directory containing the results */
- private File mResultDir = null;
- /** The directory containing the logs */
- private File mLogDir = null;
-
- private ResultUploader mUploader;
-
- private LogFileSaver mTestLogSaver;
- /** Invocation level Log saver to receive when files are logged */
- private ILogSaver mLogSaver;
- /** Invocation level configuration */
- private IConfiguration mConfiguration = null;
-
- private String mReferenceUrl;
-
- private Map<String, String> mLoggedFiles;
-
- private static final String[] RESULT_RESOURCES = {
- "compatibility_result.css",
- "compatibility_result.xsl",
- "logo.png"
- };
-
- public CertificationSuiteResultReporter() {
- super();
- mLoggedFiles = new LinkedHashMap<>();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public final void invocationStarted(IInvocationContext context) {
- super.invocationStarted(context);
-
- if (mBuildHelper == null) {
- mBuildHelper = new CompatibilityBuildHelper(getPrimaryBuildInfo());
- }
- if (mResultDir == null) {
- initializeResultDirectories();
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void testLog(String name, LogDataType type, InputStreamSource stream) {
- if (name.endsWith(DeviceInfo.FILE_SUFFIX)) {
- // Handle device info file case
- testLogDeviceInfo(name, stream);
- return;
- }
- try {
- File logFile = null;
- if (mCompressLogs) {
- try (InputStream inputStream = stream.createInputStream()) {
- logFile = mTestLogSaver.saveAndGZipLogData(name, type, inputStream);
- }
- } else {
- try (InputStream inputStream = stream.createInputStream()) {
- logFile = mTestLogSaver.saveLogData(name, type, inputStream);
- }
- }
- CLog.d("Saved logs for %s in %s", name, logFile.getAbsolutePath());
- } catch (IOException e) {
- CLog.e("Failed to write log for %s", name);
- CLog.e(e);
- }
- }
-
- /** Write device-info files to the result, invoked only by the master result reporter */
- private void testLogDeviceInfo(String name, InputStreamSource stream) {
- try {
- File ediDir = new File(mResultDir, DeviceInfo.RESULT_DIR_NAME);
- ediDir.mkdirs();
- File ediFile = new File(ediDir, name);
- if (!ediFile.exists()) {
- // only write this file to the results if not already present
- FileUtil.writeToFile(stream.createInputStream(), ediFile);
- }
- } catch (IOException e) {
- CLog.w("Failed to write device info %s to result", name);
- CLog.e(e);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void testLogSaved(String dataName, LogDataType dataType, InputStreamSource dataStream,
- LogFile logFile) {
- if (mIncludeTestLogTags) {
- switch (dataType) {
- case BUGREPORT:
- case LOGCAT:
- case PNG:
- mLoggedFiles.put(dataName, logFile.getUrl());
- break;
- default:
- // Do nothing
- break;
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void putSummary(List<TestSummary> summaries) {
- for (TestSummary summary : summaries) {
- if (mReferenceUrl == null && summary.getSummary().getString() != null) {
- mReferenceUrl = summary.getSummary().getString();
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setLogSaver(ILogSaver saver) {
- mLogSaver = saver;
- }
-
- /** {@inheritDoc} */
- @Override
- public void setConfiguration(IConfiguration configuration) {
- mConfiguration = configuration;
- }
-
- /**
- * Create directory structure where results and logs will be written.
- */
- private void initializeResultDirectories() {
- CLog.d("Initializing result directory");
- // TODO: Clean up start time handling to avoid relying on buildinfo
- getPrimaryBuildInfo().addBuildAttribute(CompatibilityBuildHelper.START_TIME_MS,
- Long.toString(getStartTime()));
- try {
- mResultDir = mBuildHelper.getResultDir();
- if (mResultDir != null) {
- mResultDir.mkdirs();
- }
- } catch (FileNotFoundException e) {
- throw new RuntimeException(e);
- }
-
- if (mResultDir == null) {
- throw new RuntimeException("Result Directory was not created");
- }
- if (!mResultDir.exists()) {
- throw new RuntimeException("Result Directory was not created: " +
- mResultDir.getAbsolutePath());
- }
-
- CLog.d("Results Directory: %s", mResultDir.getAbsolutePath());
-
- mUploader = new ResultUploader(mResultServer, mBuildHelper.getSuiteName());
- try {
- mLogDir = new File(mBuildHelper.getLogsDir(),
- CompatibilityBuildHelper.getDirSuffix(getStartTime()));
- } catch (FileNotFoundException e) {
- CLog.e(e);
- }
- if (mLogDir != null && mLogDir.mkdirs()) {
- CLog.d("Created log dir %s", mLogDir.getAbsolutePath());
- }
- if (mLogDir == null || !mLogDir.exists()) {
- throw new IllegalArgumentException(String.format("Could not create log dir %s",
- mLogDir.getAbsolutePath()));
- }
- if (mTestLogSaver == null) {
- mTestLogSaver = new LogFileSaver(mLogDir);
- }
- }
-
- @Override
- public IFormatterGenerator createFormatter() {
- return new CertificationResultXml(mBuildHelper.getSuiteName(),
- mBuildHelper.getSuiteVersion(),
- mBuildHelper.getSuitePlan(),
- mBuildHelper.getSuiteBuild(),
- mReferenceUrl,
- getLogUrl());
- }
-
- @Override
- public void preFormattingSetup(IFormatterGenerator formater) {
- super.preFormattingSetup(formater);
- // Log the summary
- TestSummary summary = getSummary();
- try {
- File summaryFile = new File(mResultDir, SUMMARY_FILE);
- FileUtil.writeToFile(summary.getSummary().toString(), summaryFile);
- } catch (IOException e) {
- CLog.e("Failed to save the summary.");
- CLog.e(e);
- }
-
- copyDynamicConfigFiles();
- copyFormattingFiles(mResultDir, mBuildHelper.getSuiteName());
- }
-
- @Override
- public File createResultDir() throws IOException {
- return mResultDir;
- }
-
- @Override
- public void postFormattingStep(File resultDir, File reportFile) {
- super.postFormattingStep(resultDir,reportFile);
-
- createChecksum(
- resultDir,
- getMergedTestRunResults(),
- getPrimaryBuildInfo().getBuildAttributes().get(BUILD_FINGERPRINT));
-
- File report = createReport(reportFile);
- if (report != null) {
- CLog.i("Viewable report: %s", report.getAbsolutePath());
- }
- File failureReport = null;
- if (mIncludeHtml) {
- // Create the html report before the zip file.
- failureReport = createFailureReport(reportFile);
- }
- File zippedResults = zipResults(mResultDir);
- if (!mIncludeHtml) {
- // Create failure report after zip file so extra data is not uploaded
- failureReport = createFailureReport(reportFile);
- }
- try {
- if (failureReport.exists()) {
- CLog.i("Test Result: %s", failureReport.getCanonicalPath());
- } else {
- CLog.i("Test Result: %s", reportFile.getCanonicalPath());
- }
- Path latestLink = createLatestLinkDirectory(mResultDir.toPath());
- if (latestLink != null) {
- CLog.i("Latest results link: " + latestLink.toAbsolutePath());
- }
-
- latestLink = createLatestLinkDirectory(mLogDir.toPath());
- if (latestLink != null) {
- CLog.i("Latest logs link: " + latestLink.toAbsolutePath());
- }
-
- saveLog(reportFile, zippedResults);
- } catch (IOException e) {
- CLog.e("Error when handling the post processing of results file:");
- CLog.e(e);
- }
-
- uploadResult(reportFile);
- }
-
- /**
- * Return the path in which log saver persists log files or null if
- * logSaver is not enabled.
- */
- private String getLogUrl() {
- if (!mUseLogSaver || mLogSaver == null) {
- return null;
- }
-
- return mLogSaver.getLogReportDir().getUrl();
- }
-
- /**
- * Update the "latest" symlink to the newest result directory. CTS specific.
- */
- private Path createLatestLinkDirectory(Path directory) {
- Path link = null;
-
- Path parent = directory.getParent();
-
- if (parent != null) {
- link = parent.resolve(LATEST_LINK_NAME);
- try {
- // if latest already exists, we have to remove it before creating
- Files.deleteIfExists(link);
- Files.createSymbolicLink(link, directory);
- } catch (IOException ioe) {
- CLog.e("Exception while attempting to create 'latest' link to: [%s]",
- directory);
- CLog.e(ioe);
- return null;
- } catch (UnsupportedOperationException uoe) {
- CLog.e("Failed to create 'latest' symbolic link - unsupported operation");
- return null;
- }
- }
- return link;
- }
-
- /**
- * move the dynamic config files to the results directory
- */
- private void copyDynamicConfigFiles() {
- File configDir = new File(mResultDir, "config");
- if (!configDir.mkdir()) {
- CLog.w("Failed to make dynamic config directory \"%s\" in the result",
- configDir.getAbsolutePath());
- }
-
- Set<String> uniqueModules = new HashSet<>();
- // Check each build of the invocation, in case of multi-device invocation.
- for (IBuildInfo buildInfo : getInvocationContext().getBuildInfos()) {
- CompatibilityBuildHelper helper = new CompatibilityBuildHelper(buildInfo);
- Map<String, File> dcFiles = helper.getDynamicConfigFiles();
- for (String moduleName : dcFiles.keySet()) {
- File srcFile = dcFiles.get(moduleName);
- if (!uniqueModules.contains(moduleName)) {
- // have not seen config for this module yet, copy into result
- File destFile = new File(configDir, moduleName + ".dynamic");
- try {
- FileUtil.copyFile(srcFile, destFile);
- uniqueModules.add(moduleName); // Add to uniqueModules if copy succeeds
- } catch (IOException e) {
- CLog.w("Failure when copying config file \"%s\" to \"%s\" for module %s",
- srcFile.getAbsolutePath(), destFile.getAbsolutePath(), moduleName);
- CLog.e(e);
- }
- }
- FileUtil.deleteFile(srcFile);
- }
- }
- }
-
- /**
- * Copy the xml formatting files stored in this jar to the results directory. CTS specific.
- *
- * @param resultsDir
- */
- private void copyFormattingFiles(File resultsDir, String suiteName) {
- for (String resultFileName : RESULT_RESOURCES) {
- InputStream configStream = CertificationResultXml.class.getResourceAsStream(
- String.format("/report/%s-%s", suiteName, resultFileName));
- if (configStream == null) {
- // If suite specific files are not available, fallback to common.
- configStream = CertificationResultXml.class.getResourceAsStream(
- String.format("/report/%s", resultFileName));
- }
- if (configStream != null) {
- File resultFile = new File(resultsDir, resultFileName);
- try {
- FileUtil.writeToFile(configStream, resultFile);
- } catch (IOException e) {
- CLog.w("Failed to write %s to file", resultFileName);
- }
- } else {
- CLog.w("Failed to load %s from jar", resultFileName);
- }
- }
- }
-
- /**
- * When enabled, save log data using log saver
- */
- private void saveLog(File resultFile, File zippedResults) throws IOException {
- if (!mUseLogSaver) {
- return;
- }
-
- FileInputStream fis = null;
- LogFile logFile = null;
- try {
- fis = new FileInputStream(resultFile);
- logFile = mLogSaver.saveLogData("log-result", LogDataType.XML, fis);
- CLog.d("Result XML URL: %s", logFile.getUrl());
- logReportFiles(mConfiguration, resultFile, resultFile.getName(), LogDataType.XML);
- } catch (IOException ioe) {
- CLog.e("error saving XML with log saver");
- CLog.e(ioe);
- } finally {
- StreamUtil.close(fis);
- }
- // Save the full results folder.
- if (zippedResults != null) {
- FileInputStream zipResultStream = null;
- try {
- zipResultStream = new FileInputStream(zippedResults);
- logFile = mLogSaver.saveLogData("results", LogDataType.ZIP, zipResultStream);
- CLog.d("Result zip URL: %s", logFile.getUrl());
- logReportFiles(mConfiguration, zippedResults, "results", LogDataType.ZIP);
- } finally {
- StreamUtil.close(zipResultStream);
- }
- }
- }
-
- /**
- * Zip the contents of the given results directory. CTS specific.
- *
- * @param resultsDir
- */
- private static File zipResults(File resultsDir) {
- File zipResultFile = null;
- try {
- // create a file in parent directory, with same name as resultsDir
- zipResultFile = new File(resultsDir.getParent(), String.format("%s.zip",
- resultsDir.getName()));
- ZipUtil.createZip(resultsDir, zipResultFile);
- } catch (IOException e) {
- CLog.w("Failed to create zip for %s", resultsDir.getName());
- }
- return zipResultFile;
- }
-
- /**
- * When enabled, upload the result to a server. CTS specific.
- */
- private void uploadResult(File resultFile) {
- if (mResultServer != null && !mResultServer.trim().isEmpty() && !mDisableResultPosting) {
- try {
- CLog.d("Result Server: %d", mUploader.uploadResult(resultFile, mReferenceUrl));
- } catch (IOException ioe) {
- CLog.e("IOException while uploading result.");
- CLog.e(ioe);
- }
- }
- }
-
- /** Generate html report. */
- private File createReport(File inputXml) {
- File report = new File(inputXml.getParentFile(), HTLM_REPORT_NAME);
- try (InputStream xslStream =
- new FileInputStream(
- new File(inputXml.getParentFile(), REPORT_XSL_FILE_NAME));
- OutputStream outputStream = new FileOutputStream(report)) {
- Transformer transformer =
- TransformerFactory.newInstance().newTransformer(new StreamSource(xslStream));
- transformer.transform(new StreamSource(inputXml), new StreamResult(outputStream));
- } catch (IOException | TransformerException ignored) {
- CLog.e(ignored);
- FileUtil.deleteFile(report);
- return null;
- }
- return report;
- }
-
- /**
- * Generate html report listing an failed tests. CTS specific.
- */
- private File createFailureReport(File inputXml) {
- File failureReport = new File(inputXml.getParentFile(), FAILURE_REPORT_NAME);
- try (InputStream xslStream = ResultHandler.class.getResourceAsStream(
- String.format("/report/%s", FAILURE_XSL_FILE_NAME));
- OutputStream outputStream = new FileOutputStream(failureReport)) {
-
- Transformer transformer = TransformerFactory.newInstance().newTransformer(
- new StreamSource(xslStream));
- transformer.transform(new StreamSource(inputXml), new StreamResult(outputStream));
- } catch (IOException | TransformerException ignored) {
- CLog.e(ignored);
- }
- return failureReport;
- }
-
- /**
- * Generates a checksum files based on the results.
- */
- private void createChecksum(File resultDir, Collection<TestRunResult> results,
- String buildFingerprint) {
- CertificationChecksumHelper.tryCreateChecksum(resultDir, results, buildFingerprint);
- }
-
- /** Re-log a result file to all reporters so they are aware of it. */
- private void logReportFiles(
- IConfiguration configuration, File resultFile, String dataName, LogDataType type) {
- if (configuration == null) {
- return;
- }
- List<ITestInvocationListener> listeners = configuration.getTestInvocationListeners();
- try (FileInputStreamSource source = new FileInputStreamSource(resultFile)) {
- for (ITestInvocationListener listener : listeners) {
- if (listener.equals(this)) {
- // Avoid logging agaisnt itself
- continue;
- }
- listener.testLog(dataName, type, source);
- }
- }
- }
-}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopier.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopier.java
deleted file mode 100644
index 295692b..0000000
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopier.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2018 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.compatibility.common.tradefed.result.suite;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.util.ChecksumReporter;
-import com.android.compatibility.common.util.ResultHandler;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.util.FileUtil;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Recursively copy all the files from a previous session into the current one if they don't exists
- * already.
- */
-public class PreviousSessionFileCopier implements ITestInvocationListener {
-
- private static final List<String> NOT_RETRY_FILES =
- Arrays.asList(
- ChecksumReporter.NAME,
- ChecksumReporter.PREV_NAME,
- ResultHandler.FAILURE_REPORT_NAME,
- CertificationSuiteResultReporter.HTLM_REPORT_NAME,
- CertificationSuiteResultReporter.FAILURE_REPORT_NAME,
- CertificationSuiteResultReporter.SUMMARY_FILE,
- CertificationChecksumHelper.NAME,
- "diffs",
- "proto");
-
- private CompatibilityBuildHelper mBuildHelper;
- private File mPreviousSessionDir = null;
-
- /** Sets the previous session directory to copy from. */
- public void setPreviousSessionDir(File previousSessionDir) {
- mPreviousSessionDir = previousSessionDir;
- }
-
- @Override
- public void invocationStarted(IInvocationContext context) {
- if (mBuildHelper == null) {
- mBuildHelper = createCompatibilityHelper(context.getBuildInfos().get(0));
- }
- }
-
- @Override
- public void invocationEnded(long elapsedTime) {
- if (mPreviousSessionDir == null) {
- CLog.e("Could not copy previous sesson files.");
- return;
- }
- File resultDir = getResultDirectory();
- copyRetryFiles(mPreviousSessionDir, resultDir);
- }
-
- @VisibleForTesting
- protected CompatibilityBuildHelper createCompatibilityHelper(IBuildInfo info) {
- return new CompatibilityBuildHelper(info);
- }
-
- /**
- * Recursively copy any other files found in the previous session's result directory to the new
- * result directory, so long as they don't already exist. For example, a "screenshots" directory
- * generated in a previous session by a passing test will not be generated on retry unless
- * copied from the old result directory.
- *
- * @param oldDir
- * @param newDir
- */
- private void copyRetryFiles(File oldDir, File newDir) {
- File[] oldChildren = oldDir.listFiles();
- for (File oldChild : oldChildren) {
- if (NOT_RETRY_FILES.contains(oldChild.getName())) {
- continue; // do not copy this file/directory or its children
- }
- File newChild = new File(newDir, oldChild.getName());
- if (!newChild.exists()) {
- // If this old file or directory doesn't exist in new dir, simply copy it
- try {
- CLog.d("Copying %s to new session.", oldChild.getName());
- if (oldChild.isDirectory()) {
- FileUtil.recursiveCopy(oldChild, newChild);
- } else {
- FileUtil.copyFile(oldChild, newChild);
- }
- } catch (IOException e) {
- CLog.w("Failed to copy file \"%s\" from previous session", oldChild.getName());
- }
- } else if (oldChild.isDirectory() && newChild.isDirectory()) {
- // If both children exist as directories, make sure the children of the old child
- // directory exist in the new child directory.
- copyRetryFiles(oldChild, newChild);
- }
- }
- }
-
- private File getResultDirectory() {
- File resultDir = null;
- try {
- resultDir = mBuildHelper.getResultDir();
- if (resultDir != null) {
- resultDir.mkdirs();
- }
- } catch (FileNotFoundException e) {
- throw new RuntimeException(e);
- }
- if (resultDir == null) {
- throw new RuntimeException("Result Directory was not created");
- }
- if (!resultDir.exists()) {
- throw new RuntimeException(
- "Result Directory was not created: " + resultDir.getAbsolutePath());
- }
- CLog.d("Results Directory: %s", resultDir.getAbsolutePath());
- return resultDir;
- }
-}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
deleted file mode 100644
index 805a2ad..0000000
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
+++ /dev/null
@@ -1,622 +0,0 @@
-/*
- * Copyright (C) 2015 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.compatibility.common.tradefed.result;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider;
-import com.android.compatibility.common.util.DeviceInfo;
-import com.android.compatibility.common.util.ICaseResult;
-import com.android.compatibility.common.util.IInvocationResult;
-import com.android.compatibility.common.util.IModuleResult;
-import com.android.compatibility.common.util.ITestResult;
-import com.android.compatibility.common.util.TestStatus;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ByteArrayInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.result.TestDescription;
-import com.android.tradefed.util.AbiUtils;
-import com.android.tradefed.util.FileUtil;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Unit tests for {@link ResultReporter}
- */
-public class ResultReporterTest extends TestCase {
-
- private static final String ROOT_PROPERTY = "TESTS_ROOT";
- private static final String SUITE_NAME = "TESTS";
- private static final String BUILD_NUMBER = "2";
- private static final String SUITE_PLAN = "cts";
- private static final String DYNAMIC_CONFIG_URL = "";
- private static final String ROOT_DIR_NAME = "root";
- private static final String BASE_DIR_NAME = "android-tests";
- private static final String TESTCASES = "testcases";
- private static final String NAME = "ModuleName";
- private static final String ABI = "mips64";
- private static final String ID = AbiUtils.createId(ABI, NAME);
- private static final String CLASS = "android.test.FoorBar";
- private static final String METHOD_1 = "testBlah1";
- private static final String METHOD_2 = "testBlah2";
- private static final String METHOD_3 = "testBlah3";
- private static final String TEST_1 = String.format("%s#%s", CLASS, METHOD_1);
- private static final String TEST_2 = String.format("%s#%s", CLASS, METHOD_2);
- private static final String TEST_3 = String.format("%s#%s", CLASS, METHOD_3);
- private static final String STACK_TRACE = "Something small is not alright\n " +
- "at four.big.insects.Marley.sing(Marley.java:10)";
- private static final String RESULT_DIR = "result123";
- private static final String[] FORMATTING_FILES = {
- "compatibility_result.css",
- "compatibility_result.xsl",
- "logo.png"};
-
- private ResultReporter mReporter;
- private IBuildInfo mBuildInfo;
- private IInvocationContext mContext;
- private CompatibilityBuildHelper mBuildHelper;
-
- private File mRoot = null;
- private File mBase = null;
- private File mTests = null;
-
- @Override
- public void setUp() throws Exception {
- mReporter = new ResultReporter();
- mRoot = FileUtil.createTempDir(ROOT_DIR_NAME);
- mBase = new File(mRoot, BASE_DIR_NAME);
- mBase.mkdirs();
- mTests = new File(mBase, TESTCASES);
- mTests.mkdirs();
- System.setProperty(ROOT_PROPERTY, mRoot.getAbsolutePath());
- CompatibilityBuildProvider provider = new CompatibilityBuildProvider() {
- @Override
- protected String getSuiteInfoName() {
- return SUITE_NAME;
- }
- @Override
- protected String getSuiteInfoBuildNumber() {
- return BUILD_NUMBER;
- }
- @Override
- protected String getSuiteInfoVersion() {
- return BUILD_NUMBER;
- }
- };
- OptionSetter setter = new OptionSetter(provider);
- setter.setOptionValue("plan", SUITE_PLAN);
- setter.setOptionValue("dynamic-config-url", DYNAMIC_CONFIG_URL);
- mBuildInfo = provider.getBuild();
- mBuildHelper = new CompatibilityBuildHelper(mBuildInfo);
- mContext = new InvocationContext();
- mContext.addDeviceBuildInfo("fakeDevice", mBuildInfo);
- }
-
- @Override
- public void tearDown() throws Exception {
- mReporter = null;
- FileUtil.recursiveDelete(mRoot);
- }
-
- public void testSetup() throws Exception {
- mReporter.invocationStarted(mContext);
- // Should have created a directory for the logs
- File[] children = mBuildHelper.getLogsDir().listFiles();
- assertTrue("Didn't create logs dir", children.length == 1 && children[0].isDirectory());
- // Should have created a directory for the results
- children = mBuildHelper.getResultsDir().listFiles();
- assertTrue("Didn't create results dir", children.length == 1 && children[0].isDirectory());
- mReporter.invocationEnded(10);
- // Should have created a zip file
- children = mBuildHelper.getResultsDir().listFiles(new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- return pathname.getName().endsWith(".zip");
- }
- });
- assertTrue("Didn't create results zip",
- children.length == 1 && children[0].isFile() && children[0].length() > 0);
- }
-
- public void testResultReporting() throws Exception {
- mReporter.invocationStarted(mContext);
- mReporter.testRunStarted(ID, 2);
- TestDescription test1 = new TestDescription(CLASS, METHOD_1);
- mReporter.testStarted(test1);
- mReporter.testEnded(test1, new HashMap<String, Metric>());
- TestDescription test2 = new TestDescription(CLASS, METHOD_2);
- mReporter.testStarted(test2);
- mReporter.testFailed(test2, STACK_TRACE);
- TestDescription test3 = new TestDescription(CLASS, METHOD_3);
- mReporter.testStarted(test3);
- mReporter.testFailed(test3, STACK_TRACE);
- mReporter.testEnded(test3, new HashMap<String, Metric>());
- mReporter.testRunEnded(10, new HashMap<String, Metric>());
- mReporter.invocationEnded(10);
- IInvocationResult result = mReporter.getResult();
- assertEquals("Expected 1 pass", 1, result.countResults(TestStatus.PASS));
- assertEquals("Expected 2 failures", 2, result.countResults(TestStatus.FAIL));
- List<IModuleResult> modules = result.getModules();
- assertEquals("Expected 1 module", 1, modules.size());
- IModuleResult module = modules.get(0);
- assertTrue(module.isDone());
- assertEquals("Incorrect ID", ID, module.getId());
- List<ICaseResult> caseResults = module.getResults();
- assertEquals("Expected 1 test case", 1, caseResults.size());
- ICaseResult caseResult = caseResults.get(0);
- List<ITestResult> testResults = caseResult.getResults();
- assertEquals("Expected 3 tests", 3, testResults.size());
- ITestResult result1 = caseResult.getResult(METHOD_1);
- assertNotNull(String.format("Expected result for %s", TEST_1), result1);
- assertEquals(String.format("Expected pass for %s", TEST_1), TestStatus.PASS,
- result1.getResultStatus());
- ITestResult result2 = caseResult.getResult(METHOD_2);
- assertNotNull(String.format("Expected result for %s", TEST_2), result2);
- assertEquals(String.format("Expected fail for %s", TEST_2), TestStatus.FAIL,
- result2.getResultStatus());
- ITestResult result3 = caseResult.getResult(METHOD_3);
- assertNotNull(String.format("Expected result for %s", TEST_3), result3);
- assertEquals(String.format("Expected fail for %s", TEST_3), TestStatus.FAIL,
- result3.getResultStatus());
- }
-
- private void makeTestRun(String[] methods, boolean[] passes) {
- mReporter.testRunStarted(ID, methods.length);
-
- for (int i = 0; i < methods.length; i++) {
- TestDescription test = new TestDescription(CLASS, methods[i]);
- mReporter.testStarted(test);
- if (!passes[i]) {
- mReporter.testFailed(test, STACK_TRACE);
- }
- mReporter.testEnded(test, new HashMap<String, Metric>());
- }
-
- mReporter.testRunEnded(10, new HashMap<String, Metric>());
- }
-
- public void testRepeatedExecutions() throws Exception {
- String[] methods = new String[] {METHOD_1, METHOD_2, METHOD_3};
-
- mReporter.invocationStarted(mContext);
-
- makeTestRun(methods, new boolean[] {true, false, true});
- makeTestRun(methods, new boolean[] {true, false, false});
- makeTestRun(methods, new boolean[] {true, true, true});
-
- mReporter.invocationEnded(10);
-
- // Verification
-
- IInvocationResult result = mReporter.getResult();
- assertEquals("Expected 1 pass", 1, result.countResults(TestStatus.PASS));
- assertEquals("Expected 2 failures", 2, result.countResults(TestStatus.FAIL));
- List<IModuleResult> modules = result.getModules();
- assertEquals("Expected 1 module", 1, modules.size());
- IModuleResult module = modules.get(0);
- assertEquals("Incorrect ID", ID, module.getId());
- List<ICaseResult> caseResults = module.getResults();
- assertEquals("Expected 1 test case", 1, caseResults.size());
- ICaseResult caseResult = caseResults.get(0);
- List<ITestResult> testResults = caseResult.getResults();
- assertEquals("Expected 3 tests", 3, testResults.size());
-
- // Test 1 details
- ITestResult result1 = caseResult.getResult(METHOD_1);
- assertNotNull(String.format("Expected result for %s", TEST_1), result1);
- assertEquals(String.format("Expected pass for %s", TEST_1), TestStatus.PASS,
- result1.getResultStatus());
-
- // Test 2 details
- ITestResult result2 = caseResult.getResult(METHOD_2);
- assertNotNull(String.format("Expected result for %s", TEST_2), result2);
- assertEquals(String.format("Expected fail for %s", TEST_2), TestStatus.FAIL,
- result2.getResultStatus());
- // TODO: Define requirement. Should this result have multiple stack traces?
- assertEquals(result2.getStackTrace(), STACK_TRACE);
-
- // Test 3 details
- ITestResult result3 = caseResult.getResult(METHOD_3);
- assertNotNull(String.format("Expected result for %s", TEST_3), result3);
- assertEquals(String.format("Expected fail for %s", TEST_3), TestStatus.FAIL,
- result3.getResultStatus());
- assertEquals(result3.getStackTrace(), STACK_TRACE);
- }
-
- public void testRetry() throws Exception {
- mReporter.invocationStarted(mContext);
-
- // Set up IInvocationResult with existing results from previous session
- mReporter.testRunStarted(ID, 2);
- IInvocationResult invocationResult = mReporter.getResult();
- IModuleResult moduleResult = invocationResult.getOrCreateModule(ID);
- ICaseResult caseResult = moduleResult.getOrCreateResult(CLASS);
- ITestResult testResult1 = caseResult.getOrCreateResult(METHOD_1);
- testResult1.setResultStatus(TestStatus.PASS);
- testResult1.setRetry(true);
- ITestResult testResult2 = caseResult.getOrCreateResult(METHOD_2);
- testResult2.setResultStatus(TestStatus.FAIL);
- testResult2.setStackTrace(STACK_TRACE);
- testResult2.setRetry(true);
-
- // Flip results for the current session
- TestDescription test1 = new TestDescription(CLASS, METHOD_1);
- mReporter.testStarted(test1);
- mReporter.testFailed(test1, STACK_TRACE);
- mReporter.testEnded(test1, new HashMap<String, Metric>());
- TestDescription test2 = new TestDescription(CLASS, METHOD_2);
- mReporter.testStarted(test2);
- mReporter.testEnded(test2, new HashMap<String, Metric>());
-
- mReporter.testRunEnded(10, new HashMap<String, Metric>());
- mReporter.invocationEnded(10);
-
- // Verification that results have been overwritten.
- IInvocationResult result = mReporter.getResult();
- assertEquals("Expected 1 pass", 1, result.countResults(TestStatus.PASS));
- assertEquals("Expected 1 failure", 1, result.countResults(TestStatus.FAIL));
- List<IModuleResult> modules = result.getModules();
- assertEquals("Expected 1 module", 1, modules.size());
- IModuleResult module = modules.get(0);
- List<ICaseResult> cases = module.getResults();
- assertEquals("Expected 1 test case", 1, cases.size());
- ICaseResult case1 = cases.get(0);
- List<ITestResult> testResults = case1.getResults();
- assertEquals("Expected 2 tests", 2, testResults.size());
-
- // Test 1 details
- ITestResult finalTestResult1 = case1.getResult(METHOD_1);
- assertNotNull(String.format("Expected result for %s", TEST_1), finalTestResult1);
- assertEquals(String.format("Expected fail for %s", TEST_1), TestStatus.FAIL,
- finalTestResult1.getResultStatus());
- assertEquals(finalTestResult1.getStackTrace(), STACK_TRACE);
-
- // Test 2 details
- ITestResult finalTestResult2 = case1.getResult(METHOD_2);
- assertNotNull(String.format("Expected result for %s", TEST_2), finalTestResult2);
- assertEquals(String.format("Expected pass for %s", TEST_2), TestStatus.PASS,
- finalTestResult2.getResultStatus());
- }
-
- public void testRetryCanSetDone() throws Exception {
- mReporter.invocationStarted(mContext);
- // Set mCanMarkDone directly (otherwise we must build result directory, write XML, and
- // perform actual retry)
- mReporter.mCanMarkDone = true;
- // Set up IInvocationResult with existing results from previous session
- IInvocationResult invocationResult = mReporter.getResult();
- IModuleResult moduleResult = invocationResult.getOrCreateModule(ID);
- moduleResult.initializeDone(false);
- ICaseResult caseResult = moduleResult.getOrCreateResult(CLASS);
- ITestResult testResult1 = caseResult.getOrCreateResult(METHOD_1);
- testResult1.setResultStatus(TestStatus.PASS);
- testResult1.setRetry(true);
- ITestResult testResult2 = caseResult.getOrCreateResult(METHOD_2);
- testResult2.setResultStatus(TestStatus.FAIL);
- testResult2.setStackTrace(STACK_TRACE);
- testResult2.setRetry(true);
-
- // Assume no additional filtering is applied to retry, and all tests for the module have
- // been collected. Thus, module "done" value should switch.
- mReporter.testRunStarted(ID, 1);
-
- TestDescription test2 = new TestDescription(CLASS, METHOD_2);
- mReporter.testStarted(test2);
- mReporter.testEnded(test2, new HashMap<String, Metric>());
-
- mReporter.testRunEnded(10, new HashMap<String, Metric>());
- mReporter.invocationEnded(10);
-
- // Verification that results have been overwritten.
- IInvocationResult result = mReporter.getResult();
- assertEquals("Expected 2 pass", 2, result.countResults(TestStatus.PASS));
- assertEquals("Expected 0 failures", 0, result.countResults(TestStatus.FAIL));
- List<IModuleResult> modules = result.getModules();
- assertEquals("Expected 1 module", 1, modules.size());
- IModuleResult module = modules.get(0);
- assertTrue("Module should be marked done", module.isDone());
- }
-
- public void testRetryCannotSetDone() throws Exception {
- mReporter.invocationStarted(mContext);
- // Set mCanMarkDone directly (otherwise we must build result directory, write XML, and
- // perform actual retry)
- mReporter.mCanMarkDone = false;
- // Set up IInvocationResult with existing results from previous session
- IInvocationResult invocationResult = mReporter.getResult();
- IModuleResult moduleResult = invocationResult.getOrCreateModule(ID);
- moduleResult.setDone(false);
- ICaseResult caseResult = moduleResult.getOrCreateResult(CLASS);
- ITestResult testResult1 = caseResult.getOrCreateResult(METHOD_1);
- testResult1.setResultStatus(TestStatus.PASS);
- testResult1.setRetry(true);
- ITestResult testResult2 = caseResult.getOrCreateResult(METHOD_2);
- testResult2.setResultStatus(TestStatus.FAIL);
- testResult2.setStackTrace(STACK_TRACE);
- testResult2.setRetry(true);
-
- // Since using retry-type failed option, we only run previously failed test
- // and don't run any non-executed tests, so module "done" value should not switch.
- mReporter.testRunStarted(ID, 1);
-
- TestDescription test2 = new TestDescription(CLASS, METHOD_2);
- mReporter.testStarted(test2);
- mReporter.testEnded(test2, new HashMap<String, Metric>());
-
- mReporter.testRunEnded(10, new HashMap<String, Metric>());
- mReporter.invocationEnded(10);
-
- // Verification that results have been overwritten.
- IInvocationResult result = mReporter.getResult();
- assertEquals("Expected 2 pass", 2, result.countResults(TestStatus.PASS));
- assertEquals("Expected 0 failures", 0, result.countResults(TestStatus.FAIL));
- List<IModuleResult> modules = result.getModules();
- assertEquals("Expected 1 module", 1, modules.size());
- IModuleResult module = modules.get(0);
- assertFalse("Module should not be marked done", module.isDone());
- }
-
- public void testResultReporting_moduleNotDone() throws Exception {
- mReporter.invocationStarted(mContext);
- mReporter.testRunStarted(ID, 2);
- TestDescription test1 = new TestDescription(CLASS, METHOD_1);
- mReporter.testStarted(test1);
- mReporter.testEnded(test1, new HashMap<String, Metric>());
- mReporter.testRunFailed("error");
- mReporter.testRunEnded(10, new HashMap<String, Metric>());
- mReporter.invocationEnded(10);
- IInvocationResult result = mReporter.getResult();
- assertEquals("Expected 1 pass", 1, result.countResults(TestStatus.PASS));
- assertEquals("Expected 0 failures", 0, result.countResults(TestStatus.FAIL));
- List<IModuleResult> modules = result.getModules();
- assertEquals("Expected 1 module", 1, modules.size());
- IModuleResult module = modules.get(0);
-
- // Ensure module is reported as not done
- assertFalse(module.isDone());
- assertEquals("Incorrect ID", ID, module.getId());
- List<ICaseResult> caseResults = module.getResults();
- assertEquals("Expected 1 test case", 1, caseResults.size());
- ICaseResult caseResult = caseResults.get(0);
- List<ITestResult> testResults = caseResult.getResults();
- assertEquals("Expected 1 tests", 1, testResults.size());
- ITestResult result1 = caseResult.getResult(METHOD_1);
- assertNotNull(String.format("Expected result for %s", TEST_1), result1);
- assertEquals(String.format("Expected pass for %s", TEST_1), TestStatus.PASS,
- result1.getResultStatus());
- }
-
- public void testResultReporting_moduleNotDone_noTests() throws Exception {
- mReporter.invocationStarted(mContext);
- mReporter.testRunStarted(ID, 0);
- mReporter.testRunFailed("error"); // test run failure should prevent marking module "done"
- mReporter.testRunEnded(10, new HashMap<String, String>());
- mReporter.invocationEnded(10);
- IInvocationResult result = mReporter.getResult();
- assertEquals("Expected 0 pass", 0, result.countResults(TestStatus.PASS));
- assertEquals("Expected 0 failures", 0, result.countResults(TestStatus.FAIL));
- List<IModuleResult> modules = result.getModules();
- assertEquals("Expected 1 module", 1, modules.size());
- IModuleResult module = modules.get(0);
- assertEquals("Incorrect ID", ID, module.getId());
- // Ensure module is reported as not done
- assertFalse(module.isDone());
- }
-
- public void testResultReporting_moduleDone_noTests() throws Exception {
- mReporter.invocationStarted(mContext);
- mReporter.testRunStarted(ID, 0);
- // Lack of test run failure should allow module to be marked "done"
- mReporter.testRunEnded(10, new HashMap<String, String>());
- mReporter.invocationEnded(10);
- IInvocationResult result = mReporter.getResult();
- assertEquals("Expected 0 pass", 0, result.countResults(TestStatus.PASS));
- assertEquals("Expected 0 failures", 0, result.countResults(TestStatus.FAIL));
- List<IModuleResult> modules = result.getModules();
- assertEquals("Expected 1 module", 1, modules.size());
- IModuleResult module = modules.get(0);
- assertEquals("Incorrect ID", ID, module.getId());
- // Ensure module is reported as done
- assertTrue(module.isDone());
- }
-
- public void testCopyFormattingFiles() throws Exception {
- File resultDir = new File(mBuildHelper.getResultsDir(), RESULT_DIR);
- resultDir.mkdirs();
- ResultReporter.copyFormattingFiles(resultDir, SUITE_NAME);
- for (String filename : FORMATTING_FILES) {
- File file = new File(resultDir, filename);
- assertTrue(String.format("%s (%s) was not created", filename, file.getAbsolutePath()),
- file.exists() && file.isFile() && file.length() > 0);
- }
- }
-
- /**
- * Ensure that when {@link ResultReporter#testLog(String, LogDataType, InputStreamSource)} is
- * called, a single invocation result folder is created and populated.
- */
- public void testTestLog() throws Exception {
- InputStreamSource fakeData = new ByteArrayInputStreamSource("test".getBytes());
- mReporter.invocationStarted(mContext);
- mReporter.testLog("test1", LogDataType.LOGCAT, fakeData);
- // date folder
- assertEquals(1, mBuildHelper.getLogsDir().list().length);
- // inv_ folder
- assertEquals(1, mBuildHelper.getLogsDir().listFiles()[0].list().length);
- // actual logs
- assertEquals(1, mBuildHelper.getLogsDir().listFiles()[0].listFiles()[0].list().length);
- mReporter.testLog("test2", LogDataType.LOGCAT, fakeData);
- // date folder
- assertEquals(1, mBuildHelper.getLogsDir().list().length);
- // inv_ folder
- assertEquals(1, mBuildHelper.getLogsDir().listFiles()[0].list().length);
- // actual logs
- assertEquals(2, mBuildHelper.getLogsDir().listFiles()[0].listFiles()[0].list().length);
- }
-
- /**
- * Ensure that when {@link ResultReporter#testLog(String, LogDataType, InputStreamSource)} is
- * called for host-side device info, a device info file is created in the result
- */
- public void testTestLogWithDeviceInfo() throws Exception {
- InputStreamSource fakeData = new ByteArrayInputStreamSource("test".getBytes());
- String deviceInfoName = String.format("Test%s", DeviceInfo.FILE_SUFFIX);
- mReporter.invocationStarted(mContext);
- mReporter.testLog(deviceInfoName, LogDataType.TEXT, fakeData);
- File deviceInfoFolder = new File(mBuildHelper.getResultDir(), DeviceInfo.RESULT_DIR_NAME);
- // assert device info folder was created
- assertTrue(deviceInfoFolder.exists());
- File[] deviceInfoFiles = deviceInfoFolder.listFiles();
- // assert that one file was written to the folder
- assertEquals(1, deviceInfoFiles.length);
- File deviceInfoFile = deviceInfoFiles[0];
- // assert device info file has been named correctly
- assertEquals(deviceInfoName, deviceInfoFile.getName());
- // assert contents of the file
- assertEquals("test", FileUtil.readStringFromFile(deviceInfoFile));
- }
-
- /** Ensure that the module is not marked done if any of the shard fails. */
- public void testResultReporter_sharded() throws Exception {
- ResultReporter shard1 = new ResultReporter(mReporter);
- ResultReporter shard2 = new ResultReporter(mReporter);
-
- mReporter.invocationStarted(mContext);
- shard1.invocationStarted(mContext);
- shard2.invocationStarted(mContext);
-
- // First shard is good
- shard1.testRunStarted(ID, 1);
- TestDescription test1 = new TestDescription(CLASS, METHOD_1);
- shard1.testStarted(test1);
- shard1.testEnded(test1, new HashMap<String, Metric>());
- shard1.testRunEnded(10, new HashMap<String, Metric>());
- shard1.invocationEnded(10);
- // Second shard failed
- shard2.testRunStarted(ID, 2);
- TestDescription test2 = new TestDescription(CLASS, METHOD_2);
- shard2.testStarted(test2);
- shard2.testEnded(test2, new HashMap<String, Metric>());
- shard2.testRunFailed("error");
- shard2.testRunEnded(10, new HashMap<String, Metric>());
- shard2.invocationEnded(10);
-
- IInvocationResult result = mReporter.getResult();
- assertEquals("Expected 2 pass", 2, result.countResults(TestStatus.PASS));
- assertEquals("Expected 0 failures", 0, result.countResults(TestStatus.FAIL));
- List<IModuleResult> modules = result.getModules();
- assertEquals("Expected 1 module", 1, modules.size());
- IModuleResult module = modules.get(0);
-
- // Ensure module is seen as not done and failed
- assertFalse(module.isDone());
- assertTrue(module.isFailed());
-
- assertEquals("Incorrect ID", ID, module.getId());
- List<ICaseResult> caseResults = module.getResults();
- assertEquals("Expected 1 test run", 1, caseResults.size());
- ICaseResult caseResult = caseResults.get(0);
- List<ITestResult> testResults = caseResult.getResults();
- assertEquals("Expected 2 test cases", 2, testResults.size());
- ITestResult result1 = caseResult.getResult(METHOD_1);
- assertNotNull(String.format("Expected result for %s", TEST_1), result1);
- assertEquals(
- String.format("Expected pass for %s", TEST_1),
- TestStatus.PASS,
- result1.getResultStatus());
- }
-
- /** Ensure that the run history of the current run is added to previous run history. */
- public void testRetryWithRunHistory() throws Exception {
- mReporter.invocationStarted(mContext);
-
- // Set up IInvocationResult with existing results from previous session
- mReporter.testRunStarted(ID, 2);
- IInvocationResult invocationResult = mReporter.getResult();
- IModuleResult moduleResult = invocationResult.getOrCreateModule(ID);
- ICaseResult caseResult = moduleResult.getOrCreateResult(CLASS);
- ITestResult testResult1 = caseResult.getOrCreateResult(METHOD_1);
- testResult1.setResultStatus(TestStatus.PASS);
- testResult1.setRetry(true);
- ITestResult testResult2 = caseResult.getOrCreateResult(METHOD_2);
- testResult2.setResultStatus(TestStatus.FAIL);
- testResult2.setStackTrace(STACK_TRACE);
- testResult2.setRetry(true);
- // Set up IInvocationResult with the run history of previous runs.
- invocationResult.addInvocationInfo(
- "run_history", "[{\"startTime\":1,\"endTime\":2},{\"startTime\":3,\"endTime\":4}]");
-
- // Flip results for the current session
- TestDescription test1 = new TestDescription(CLASS, METHOD_1);
- mReporter.testStarted(test1);
- mReporter.testFailed(test1, STACK_TRACE);
- mReporter.testEnded(test1, new HashMap<String, Metric>());
- TestDescription test2 = new TestDescription(CLASS, METHOD_2);
- mReporter.testStarted(test2);
- mReporter.testEnded(test2, new HashMap<String, Metric>());
-
- mReporter.testRunEnded(10, new HashMap<String, Metric>());
- mReporter.invocationEnded(10);
-
- // Verification that results have been overwritten.
- IInvocationResult result = mReporter.getResult();
- assertEquals("Expected 1 pass", 1, result.countResults(TestStatus.PASS));
- assertEquals("Expected 1 failure", 1, result.countResults(TestStatus.FAIL));
- List<IModuleResult> modules = result.getModules();
- assertEquals("Expected 1 module", 1, modules.size());
- IModuleResult module = modules.get(0);
- List<ICaseResult> cases = module.getResults();
- assertEquals("Expected 1 test case", 1, cases.size());
- ICaseResult case1 = cases.get(0);
- List<ITestResult> testResults = case1.getResults();
- assertEquals("Expected 2 tests", 2, testResults.size());
-
- long startTime = mReporter.getResult().getStartTime();
- String expectedRunHistory =
- String.format(
- "[{\"startTime\":1,\"endTime\":2},"
- + "{\"startTime\":3,\"endTime\":4},{\"startTime\":%d,\"endTime\":%d}]",
- startTime, startTime + 10);
- assertEquals(expectedRunHistory, invocationResult.getInvocationInfo().get("run_history"));
-
- // Test 1 details
- ITestResult finalTestResult1 = case1.getResult(METHOD_1);
- assertNotNull(String.format("Expected result for %s", TEST_1), finalTestResult1);
- assertEquals(
- String.format("Expected fail for %s", TEST_1),
- TestStatus.FAIL,
- finalTestResult1.getResultStatus());
- assertEquals(finalTestResult1.getStackTrace(), STACK_TRACE);
-
- // Test 2 details
- ITestResult finalTestResult2 = case1.getResult(METHOD_2);
- assertNotNull(String.format("Expected result for %s", TEST_2), finalTestResult2);
- assertEquals(
- String.format("Expected pass for %s", TEST_2),
- TestStatus.PASS,
- finalTestResult2.getResultStatus());
- }
-}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopierTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopierTest.java
deleted file mode 100644
index a0c3131..0000000
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopierTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2018 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.compatibility.common.tradefed.result.suite;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.build.BuildInfo;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.ConfigurationDef;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
-import com.android.tradefed.util.FileUtil;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-
-/** Unit tests for {@link PreviousSessionFileCopier}. */
-@RunWith(JUnit4.class)
-public class PreviousSessionFileCopierTest {
-
- private PreviousSessionFileCopier mCopier;
- private File mPreviousDir;
- private File mCurrentDir;
- private IInvocationContext mContext;
-
- @Before
- public void setUp() throws Exception {
- mCurrentDir = FileUtil.createTempDir("current-copier-tests");
- mCopier =
- new PreviousSessionFileCopier() {
- @Override
- protected CompatibilityBuildHelper createCompatibilityHelper(IBuildInfo info) {
- return new CompatibilityBuildHelper(info) {
- @Override
- public File getResultDir() throws FileNotFoundException {
- return mCurrentDir;
- }
- };
- }
- };
- mPreviousDir = FileUtil.createTempDir("previous-copier-tests");
- mContext = new InvocationContext();
- mContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, new BuildInfo());
- mCopier.setPreviousSessionDir(mPreviousDir);
- }
-
- @After
- public void tearDown() {
- FileUtil.recursiveDelete(mPreviousDir);
- }
-
- @Test
- public void testCopy() throws Exception {
- new File(mPreviousDir, "newFile").createNewFile();
- assertEquals(0, mCurrentDir.listFiles().length);
- mCopier.invocationStarted(mContext);
- mCopier.invocationEnded(500L);
- assertEquals(1, mCurrentDir.listFiles().length);
- }
-
- @Test
- public void testCopy_fileExists() throws Exception {
- File original = new File(mCurrentDir, "newFile");
- original.createNewFile();
- FileUtil.writeToFile("CURRENT", original);
-
- File previous = new File(mPreviousDir, "newFile");
- previous.createNewFile();
- FileUtil.writeToFile("PREVIOUS", previous);
-
- assertEquals(1, mCurrentDir.listFiles().length);
- mCopier.invocationStarted(mContext);
- mCopier.invocationEnded(500L);
- assertEquals(1, mCurrentDir.listFiles().length);
- // File are not overriden
- assertEquals("CURRENT", FileUtil.readStringFromFile(original));
- }
-
- @Test
- public void testCopy_fileExcluded() throws Exception {
- new File(mPreviousDir, "proto").mkdir();
- mCopier.invocationStarted(mContext);
- mCopier.invocationEnded(500L);
- // Nothing was copied
- assertEquals(0, mCurrentDir.listFiles().length);
- }
-}
diff --git a/common_util/com/android/tradefed/config/ConfigurationException.java b/common_util/com/android/tradefed/config/ConfigurationException.java
index 2af9c01..696ee59 100644
--- a/common_util/com/android/tradefed/config/ConfigurationException.java
+++ b/common_util/com/android/tradefed/config/ConfigurationException.java
@@ -15,10 +15,13 @@
*/
package com.android.tradefed.config;
-/**
- * Thrown if configuration could not be loaded.
- */
-public class ConfigurationException extends Exception {
+import com.android.tradefed.error.HarnessException;
+import com.android.tradefed.result.error.ErrorIdentifier;
+
+import java.lang.StackWalker.Option;
+
+/** Thrown if configuration could not be loaded. */
+public class ConfigurationException extends HarnessException {
private static final long serialVersionUID = 7742154448569011969L;
/**
@@ -27,7 +30,19 @@
* @param msg a meaningful error message
*/
public ConfigurationException(String msg) {
- super(msg);
+ super(msg, null);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
+ }
+
+ /**
+ * Creates a {@link ConfigurationException}.
+ *
+ * @param msg a meaningful error message
+ * @param error The {@link ErrorIdentifier} associated with the exception
+ */
+ public ConfigurationException(String msg, ErrorIdentifier error) {
+ super(msg, error);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
}
/**
@@ -37,7 +52,20 @@
* @param cause the {@link Throwable} that represents the original cause of the error
*/
public ConfigurationException(String msg, Throwable cause) {
- super(msg, cause);
+ super(msg, cause, null);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
+ }
+
+ /**
+ * Creates a {@link ConfigurationException}.
+ *
+ * @param msg a meaningful error message
+ * @param cause the {@link Throwable} that represents the original cause of the error
+ * @param error The {@link ErrorIdentifier} associated with the exception
+ */
+ public ConfigurationException(String msg, Throwable cause, ErrorIdentifier error) {
+ super(msg, cause, error);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
}
}
diff --git a/device_build_interfaces/com/android/tradefed/error/HarnessException.java b/common_util/com/android/tradefed/error/HarnessException.java
similarity index 93%
rename from device_build_interfaces/com/android/tradefed/error/HarnessException.java
rename to common_util/com/android/tradefed/error/HarnessException.java
index 4746671..f6fc35e 100644
--- a/device_build_interfaces/com/android/tradefed/error/HarnessException.java
+++ b/common_util/com/android/tradefed/error/HarnessException.java
@@ -70,4 +70,10 @@
mOrigin = clazz.getCanonicalName();
}
}
+
+ protected final void setCallerClass(String clazz) {
+ if (clazz != null && mOrigin == null) {
+ mOrigin = clazz;
+ }
+ }
}
diff --git a/device_build_interfaces/com/android/tradefed/error/HarnessRuntimeException.java b/common_util/com/android/tradefed/error/HarnessRuntimeException.java
similarity index 94%
rename from device_build_interfaces/com/android/tradefed/error/HarnessRuntimeException.java
rename to common_util/com/android/tradefed/error/HarnessRuntimeException.java
index 1da5df9..9b88de7 100644
--- a/device_build_interfaces/com/android/tradefed/error/HarnessRuntimeException.java
+++ b/common_util/com/android/tradefed/error/HarnessRuntimeException.java
@@ -78,4 +78,10 @@
public String getOrigin() {
return mOrigin;
}
+
+ protected final void setCallerClass(Class<?> clazz) {
+ if (clazz != null) {
+ mOrigin = clazz.getCanonicalName();
+ }
+ }
}
diff --git a/device_build_interfaces/com/android/tradefed/error/IHarnessException.java b/common_util/com/android/tradefed/error/IHarnessException.java
similarity index 100%
rename from device_build_interfaces/com/android/tradefed/error/IHarnessException.java
rename to common_util/com/android/tradefed/error/IHarnessException.java
diff --git a/common_util/com/android/tradefed/result/LogDataType.java b/common_util/com/android/tradefed/result/LogDataType.java
index da1eb9e..0bcb9a8 100644
--- a/common_util/com/android/tradefed/result/LogDataType.java
+++ b/common_util/com/android/tradefed/result/LogDataType.java
@@ -32,7 +32,7 @@
JPEG("jpeg", "image/jpeg", true, false),
TAR_GZ("tar.gz", "application/gzip", true, false),
GZIP("gz", "application/gzip", true, false),
- HPROF("hprof", "text/plain", true, false),
+ HPROF("hprof", "application/octet-stream", true, false),
COVERAGE("ec", "text/plain", false, false), // Emma coverage file
NATIVE_COVERAGE("zip", "application/zip", true, false), // gcov coverage archive
CLANG_COVERAGE("profdata", "text/plain", false, false), // LLVM indexed profile data
@@ -60,6 +60,7 @@
ATRACE("atr", "text/plain", true, false), // atrace -z format
KERNEL_TRACE("dat", "text/plain", false, false), // raw kernel ftrace buffer
DIR("", "text/plain", false, false),
+ CFG("cfg", "application/octet-stream", false, true),
/* Unknown file type */
UNKNOWN("dat", "text/plain", false, false);
diff --git a/common_util/com/android/tradefed/result/error/DeviceErrorIdentifier.java b/common_util/com/android/tradefed/result/error/DeviceErrorIdentifier.java
new file mode 100644
index 0000000..483d02b
--- /dev/null
+++ b/common_util/com/android/tradefed/result/error/DeviceErrorIdentifier.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 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.result.error;
+
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
+
+/** Error Identifiers from Device errors and device reported errors. */
+public enum DeviceErrorIdentifier implements ErrorIdentifier {
+
+ // ********************************************************************************************
+ // Device Errors: 520_001 ~ 530_000
+ // ********************************************************************************************
+ APK_INSTALLATION_FAILED(520_001, FailureStatus.DEPENDENCY_ISSUE),
+ FAIL_ACTIVATE_APEX(520_002, FailureStatus.DEPENDENCY_ISSUE),
+
+ AAPT_PARSER_FAILED(520_050, FailureStatus.DEPENDENCY_ISSUE),
+
+ SHELL_COMMAND_ERROR(520_100, FailureStatus.DEPENDENCY_ISSUE),
+ DEVICE_UNEXPECTED_RESPONSE(30_101, FailureStatus.DEPENDENCY_ISSUE),
+ FAIL_PUSH_FILE(30_102, FailureStatus.DEPENDENCY_ISSUE),
+ FAIL_PULL_FILE(30_103, FailureStatus.DEPENDENCY_ISSUE),
+ DEVICE_FAILED_TO_RESET(30_104, FailureStatus.DEPENDENCY_ISSUE),
+
+ INSTRUMENTATION_CRASH(520_200, FailureStatus.SYSTEM_UNDER_TEST_CRASHED),
+
+ FAILED_TO_LAUNCH_GCE(520_500, FailureStatus.LOST_SYSTEM_UNDER_TEST),
+ FAILED_TO_CONNECT_TO_GCE(520_501, FailureStatus.LOST_SYSTEM_UNDER_TEST),
+ ERROR_AFTER_FLASHING(520_502, FailureStatus.LOST_SYSTEM_UNDER_TEST),
+
+ DEVICE_UNAVAILABLE(520_750, FailureStatus.LOST_SYSTEM_UNDER_TEST),
+ DEVICE_UNRESPONSIVE(520_751, FailureStatus.LOST_SYSTEM_UNDER_TEST);
+
+ private final long code;
+ private final FailureStatus status;
+
+ DeviceErrorIdentifier(int code, FailureStatus status) {
+ this.code = code;
+ this.status = status;
+ }
+
+ @Override
+ public long code() {
+ return code;
+ }
+
+ @Override
+ public FailureStatus status() {
+ return status;
+ }
+}
diff --git a/test_result_interfaces/com/android/tradefed/result/error/ErrorIdentifier.java b/common_util/com/android/tradefed/result/error/ErrorIdentifier.java
similarity index 92%
rename from test_result_interfaces/com/android/tradefed/result/error/ErrorIdentifier.java
rename to common_util/com/android/tradefed/result/error/ErrorIdentifier.java
index 4d00bdd..48e0b40 100644
--- a/test_result_interfaces/com/android/tradefed/result/error/ErrorIdentifier.java
+++ b/common_util/com/android/tradefed/result/error/ErrorIdentifier.java
@@ -15,7 +15,6 @@
*/
package com.android.tradefed.result.error;
-import com.android.tradefed.result.FailureDescription;
import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
/**
@@ -33,7 +32,7 @@
/**
* The failure status associated with the identifier, this status is expected to align with the
- * {@link FailureDescription} one.
+ * FailureDescription one.
*/
public FailureStatus status();
}
diff --git a/common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java b/common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java
new file mode 100644
index 0000000..619b4fb
--- /dev/null
+++ b/common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 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.result.error;
+
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
+
+/** Error Identifiers from Trade Federation infra, and dependent infra (like Build infra). */
+public enum InfraErrorIdentifier implements ErrorIdentifier {
+
+ // ********************************************************************************************
+ // Infra: 500_001 ~ 510_000
+ // ********************************************************************************************
+ // 500_001 - 500_500: General errors
+ ARTIFACT_NOT_FOUND(500_001, FailureStatus.DEPENDENCY_ISSUE),
+ FAIL_TO_CREATE_FILE(500_002, FailureStatus.INFRA_FAILURE),
+ INVOCATION_CANCELLED(500_003, FailureStatus.CANCELLED),
+ CODE_COVERAGE_ERROR(500_004, FailureStatus.INFRA_FAILURE),
+ MODULE_SETUP_RUNTIME_EXCEPTION(500_005, FailureStatus.CUSTOMER_ISSUE),
+ CONFIGURED_ARTIFACT_NOT_FOUND(500_006, FailureStatus.CUSTOMER_ISSUE),
+ INVOCATION_TIMEOUT(500_007, FailureStatus.TIMED_OUT),
+ OPTION_CONFIGURATION_ERROR(500_008, FailureStatus.CUSTOMER_ISSUE),
+ RUNNER_ALLOCATION_ERROR(500_009, FailureStatus.INFRA_FAILURE),
+ SCHEDULER_ALLOCATION_ERROR(500_010, FailureStatus.CUSTOMER_ISSUE),
+
+ // 500_501 - 501_000: Build, Artifacts download related errors
+ ARTIFACT_REMOTE_PATH_NULL(500_501, FailureStatus.INFRA_FAILURE),
+ ARTIFACT_UNSUPPORTED_PATH(500_502, FailureStatus.INFRA_FAILURE),
+ ARTIFACT_DOWNLOAD_ERROR(500_503, FailureStatus.DEPENDENCY_ISSUE),
+ GCS_ERROR(500_504, FailureStatus.DEPENDENCY_ISSUE),
+
+ // 501_001 - 501_500: environment issues: For example: lab wifi
+ WIFI_FAILED_CONNECT(501_001, FailureStatus.DEPENDENCY_ISSUE),
+ GOOGLE_ACCOUNT_SETUP_FAILED(501_002, FailureStatus.DEPENDENCY_ISSUE),
+ NO_WIFI(501_003, FailureStatus.DEPENDENCY_ISSUE),
+
+ // 502_000 - 502_100: Test issues detected by infra
+ EXPECTED_TESTS_MISMATCH(502_000, FailureStatus.TEST_FAILURE),
+
+ // 505_000 - 505_250: Acloud errors
+ NO_ACLOUD_REPORT(505_000, FailureStatus.DEPENDENCY_ISSUE),
+
+ UNDETERMINED(510_000, FailureStatus.UNSET);
+
+ private final long code;
+ private final FailureStatus status;
+
+ InfraErrorIdentifier(int code, FailureStatus status) {
+ this.code = code;
+ this.status = status;
+ }
+
+ @Override
+ public long code() {
+ return code;
+ }
+
+ @Override
+ public FailureStatus status() {
+ return status;
+ }
+}
diff --git a/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java b/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java
new file mode 100644
index 0000000..5f80ba4
--- /dev/null
+++ b/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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.result.error;
+
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
+
+/** Error identifier from tests and tests runners. */
+public enum TestErrorIdentifier implements ErrorIdentifier {
+ MODULE_DID_NOT_EXECUTE(530_001, FailureStatus.NOT_EXECUTED);
+
+ private final long code;
+ private final FailureStatus status;
+
+ TestErrorIdentifier(int code, FailureStatus status) {
+ this.code = code;
+ this.status = status;
+ }
+
+ @Override
+ public long code() {
+ return code;
+ }
+
+ @Override
+ public FailureStatus status() {
+ return status;
+ }
+}
diff --git a/common_util/com/android/tradefed/util/Email.java b/common_util/com/android/tradefed/util/Email.java
index 879e7b5..2c2c79f 100644
--- a/common_util/com/android/tradefed/util/Email.java
+++ b/common_util/com/android/tradefed/util/Email.java
@@ -94,7 +94,7 @@
*/
@Override
public void send(Message msg) throws IllegalArgumentException, IOException {
- // Sanity checks
+ // Validity checks
if (msg.getTo() == null) {
throw new IllegalArgumentException("Message is missing a destination");
} else if (msg.getSubject() == null) {
diff --git a/common_util/com/android/tradefed/util/FileUtil.java b/common_util/com/android/tradefed/util/FileUtil.java
index 70ee95c..017224d 100644
--- a/common_util/com/android/tradefed/util/FileUtil.java
+++ b/common_util/com/android/tradefed/util/FileUtil.java
@@ -243,10 +243,8 @@
protected static boolean chmodExists() {
// Silence the scary process exception when chmod is missing, we will log instead.
CommandResult result = RunUtil.getDefault().runTimedCmdSilently(10 * 1000, sChmod);
- // We expect a status fail because 'chmod' requires arguments.
- String stderr = result.getStderr();
- if (CommandStatus.FAILED.equals(result.getStatus()) &&
- (stderr.contains("chmod: missing operand") || stderr.contains("usage: "))) {
+ // Exit code 127 means “command not found”.
+ if (result.getExitCode() != null && result.getExitCode() != 127) {
return true;
}
CLog.w("Chmod is not supported by this OS.");
diff --git a/common_util/com/android/tradefed/util/SparseImageUtil.java b/common_util/com/android/tradefed/util/SparseImageUtil.java
new file mode 100644
index 0000000..ce557a5
--- /dev/null
+++ b/common_util/com/android/tradefed/util/SparseImageUtil.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * Utility to unsparse sparse images.
+ *
+ * <p>This piece of code is adopted from:
+ * frameworks/base/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
+ */
+public class SparseImageUtil {
+ private static final int SPARSE_IMAGE_MAGIC = 0xED26FF3A;
+
+ /**
+ * Tests if file is a sparse image.
+ *
+ * @param imgFile a {@link File} that is to be tested.
+ * @return true if imgFile is a sparse image.
+ */
+ public static boolean isSparse(File imgFile) {
+ if (!imgFile.isFile()) {
+ return false;
+ }
+ try (FileInputStream in = new FileInputStream(imgFile)) {
+ // Check magic bytes
+ return readBuffer(in, 4).getInt() == SPARSE_IMAGE_MAGIC;
+ } catch (IOException e) {
+ // Return false if failed to read file
+ return false;
+ }
+ }
+
+ /**
+ * Unsparses a sparse image file.
+ *
+ * @param imgFile a {@link File} that is a sparse image.
+ * @param destFile a {@link File} to write the unsparsed image to.
+ * @throws IOException if imgFile is not a sparse image.
+ */
+ public static void unsparse(File imgFile, File destFile) throws IOException {
+ try (FileInputStream in = new FileInputStream(imgFile)) {
+ SparseInputStream sis = new SparseInputStream(new BufferedInputStream(in));
+ if (!sis.isSparse()) {
+ throw new IOException("Not a sparse image: " + imgFile);
+ }
+ FileUtil.writeToFile(sis, destFile);
+ }
+ }
+
+ /** Reads exact number of bytes. */
+ private static byte[] readFully(InputStream in, int size) throws IOException {
+ byte[] buf = new byte[size];
+ int n = 0;
+ int off = 0;
+ int left = size;
+ while (left > 0) {
+ n = in.read(buf, off, left);
+ if (n < 0) {
+ throw new IOException("Unexpected EOF in readFully()");
+ }
+ off += n;
+ left -= n;
+ }
+ return buf;
+ }
+
+ /** Helper that wraps result of readFully() in a ByteBuffer for easy consumption. */
+ private static ByteBuffer readBuffer(InputStream in, int size) throws IOException {
+ return ByteBuffer.wrap(readFully(in, size)).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ /**
+ * SparseInputStream read from upstream and detects the data format. If the upstream is a valid
+ * sparse data, it will unsparse it on the fly. Otherwise, it just passthrough as is.
+ */
+ private static class SparseInputStream extends InputStream {
+ private static final int FILE_HDR_SIZE = 28;
+ private static final int CHUNK_HDR_SIZE = 12;
+
+ /**
+ * This class represents a chunk in the Android sparse image.
+ *
+ * @see system/core/libsparse/sparse_format.h
+ */
+ private static class SparseChunk {
+ public static final short RAW = (short) 0xCAC1;
+ public static final short FILL = (short) 0xCAC2;
+ public static final short DONTCARE = (short) 0xCAC3;
+ public short mChunkType;
+ public int mChunkSize;
+ public int mTotalSize;
+ public byte[] mFill;
+
+ @Override
+ public String toString() {
+ return String.format(
+ "type: %x, chunk_size: %d, total_size: %d",
+ mChunkType, mChunkSize, mTotalSize);
+ }
+
+ public static SparseChunk readChunk(InputStream in) throws IOException {
+ SparseChunk chunk = new SparseChunk();
+ ByteBuffer buf = readBuffer(in, CHUNK_HDR_SIZE);
+ chunk.mChunkType = buf.getShort();
+ /* padding = */ buf.getShort();
+ chunk.mChunkSize = buf.getInt();
+ chunk.mTotalSize = buf.getInt();
+ if (chunk.mChunkType == FILL) {
+ chunk.mFill = readFully(in, 4);
+ }
+ return chunk;
+ }
+ }
+
+ private BufferedInputStream mIn;
+ private boolean mIsSparse;
+ private long mBlockSize;
+ private long mTotalBlocks;
+ private long mTotalChunks;
+ private SparseChunk mCur;
+ private long mLeft;
+ private int mCurChunks;
+
+ public SparseInputStream(BufferedInputStream in) throws IOException {
+ mIn = in;
+ in.mark(FILE_HDR_SIZE * 2);
+ ByteBuffer buf = readBuffer(mIn, FILE_HDR_SIZE);
+ mIsSparse = (buf.getInt() == SPARSE_IMAGE_MAGIC);
+ if (!mIsSparse) {
+ mIn.reset();
+ return;
+ }
+ int major = buf.getShort();
+ int minor = buf.getShort();
+ if (major > 0x1 || minor > 0x0) {
+ throw new IOException("Unsupported sparse version: " + major + "." + minor);
+ }
+ if (buf.getShort() != FILE_HDR_SIZE) {
+ throw new IOException("Illegal file header size");
+ }
+ if (buf.getShort() != CHUNK_HDR_SIZE) {
+ throw new IOException("Illegal chunk header size");
+ }
+ mBlockSize = buf.getInt();
+ if ((mBlockSize & 0x3) != 0) {
+ throw new IOException("Illegal block size, must be a multiple of 4: " + mBlockSize);
+ }
+ mTotalBlocks = buf.getInt();
+ mTotalChunks = buf.getInt();
+ mLeft = 0;
+ mCurChunks = 0;
+ }
+
+ /**
+ * Check if it needs to open a new chunk.
+ *
+ * @return true if it's EOF
+ */
+ private boolean prepareChunk() throws IOException {
+ if (mCur == null || mLeft <= 0) {
+ if (++mCurChunks > mTotalChunks) {
+ return true;
+ }
+ mCur = SparseChunk.readChunk(mIn);
+ mLeft = mCur.mChunkSize * mBlockSize;
+ }
+ return mLeft == 0;
+ }
+
+ @Override
+ public int read(byte[] buf, int off, int len) throws IOException {
+ if (!mIsSparse) {
+ return mIn.read(buf, off, len);
+ }
+ if (prepareChunk()) {
+ return -1;
+ }
+ int n = -1;
+ switch (mCur.mChunkType) {
+ case SparseChunk.RAW:
+ n = mIn.read(buf, off, (int) Math.min(mLeft, len));
+ mLeft -= n;
+ break;
+ case SparseChunk.DONTCARE:
+ n = (int) Math.min(mLeft, len);
+ Arrays.fill(buf, off, off + n, (byte) 0);
+ mLeft -= n;
+ break;
+ case SparseChunk.FILL:
+ // The FILL type is rarely used, so use a simple implementation.
+ n = super.read(buf, off, len);
+ break;
+ default:
+ throw new IOException("Unsupported Chunk:" + mCur);
+ }
+ return n;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (!mIsSparse) {
+ return mIn.read();
+ }
+ if (prepareChunk()) {
+ return -1;
+ }
+ int ret = -1;
+ switch (mCur.mChunkType) {
+ case SparseChunk.RAW:
+ ret = mIn.read();
+ break;
+ case SparseChunk.DONTCARE:
+ ret = 0;
+ break;
+ case SparseChunk.FILL:
+ ret = mCur.mFill[(4 - ((int) mLeft & 0x3)) & 0x3];
+ break;
+ default:
+ throw new IOException("Unsupported Chunk:" + mCur);
+ }
+ mLeft--;
+ return ret;
+ }
+
+ /**
+ * Get the unsparse size
+ *
+ * @return -1 if stream doesn't contain sparse image data.
+ */
+ public long getUnsparseSize() {
+ if (!mIsSparse) {
+ return -1;
+ }
+ return mBlockSize * mTotalBlocks;
+ }
+
+ public boolean isSparse() {
+ return mIsSparse;
+ }
+ }
+}
diff --git a/common_util/com/android/tradefed/util/ZipUtil.java b/common_util/com/android/tradefed/util/ZipUtil.java
index 31f3ff8..a6902ec 100644
--- a/common_util/com/android/tradefed/util/ZipUtil.java
+++ b/common_util/com/android/tradefed/util/ZipUtil.java
@@ -526,6 +526,7 @@
return;
} else if (zipEntry.getCompressedSize() == 0) {
// The file is empty, just create an empty file.
+ FileUtil.mkdirsRWX(targetFile.getParentFile());
targetFile.createNewFile();
return;
}
diff --git a/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java b/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java
index fc2687c..9638057 100644
--- a/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java
+++ b/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java
@@ -16,7 +16,6 @@
package com.android.tradefed.util.zip;
-import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.ByteArrayUtil;
import com.google.common.annotations.VisibleForTesting;
@@ -241,9 +240,6 @@
if (Long.toHexString(mUncompressedSize).equals("ffffffff") ||
Long.toHexString(mCompressedSize).equals("ffffffff") ||
Long.toHexString(mLocalHeaderOffset).equals("ffffffff")) {
- CLog.i("Values(compressed/uncompressed size, and relative offset of local header)) in "
- + "CentralDirectoryInfo for file name: %s reaches the limitation(0xffffffff), "
- + "getting the data from extra field.", mFileName);
byte[] zip64FieldId = Arrays.copyOfRange(
data, startOffset + mFileNameLength + 46, startOffset + mFileNameLength + 48);
// There should be a ZIP64 Field ID(0x0001) existing here.
diff --git a/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java b/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java
index 1e853c8..4a996f4 100644
--- a/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java
+++ b/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java
@@ -15,31 +15,29 @@
*/
package com.android.tradefed.device;
+import com.android.tradefed.error.HarnessRuntimeException;
+import com.android.tradefed.result.error.ErrorIdentifier;
+
+import java.lang.StackWalker.Option;
+
/**
* Thrown when a device action did not results in the expected results.
*
* <p>For example: 'pm list users' is vastly expected to return the list of users, failure to do so
* should be raised as a DeviceRuntimeException since something went very wrong.
*/
-public class DeviceRuntimeException extends RuntimeException {
+public class DeviceRuntimeException extends HarnessRuntimeException {
private static final long serialVersionUID = -7928528651742852301L;
/**
* Creates a {@link DeviceRuntimeException}.
*
* @param msg a descriptive error message of the error.
+ * @param errorId The {@link ErrorIdentifier} categorizing the exception.
*/
- public DeviceRuntimeException(String msg) {
- super(msg);
- }
-
- /**
- * Creates a {@link DeviceRuntimeException}.
- *
- * @param t {@link Throwable} that should be wrapped in {@link DeviceRuntimeException}.
- */
- public DeviceRuntimeException(Throwable t) {
- super(t);
+ public DeviceRuntimeException(String msg, ErrorIdentifier errorId) {
+ super(msg, errorId);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
}
/**
@@ -47,8 +45,10 @@
*
* @param msg a descriptive error message of the error
* @param t {@link Throwable} that should be wrapped in {@link DeviceRuntimeException}.
+ * @param errorId The {@link ErrorIdentifier} categorizing the exception.
*/
- public DeviceRuntimeException(String msg, Throwable t) {
- super(msg, t);
+ public DeviceRuntimeException(String msg, Throwable t, ErrorIdentifier errorId) {
+ super(msg, t, errorId);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
}
}
diff --git a/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java b/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
index 3e37f20..3163b52 100644
--- a/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
+++ b/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
@@ -244,6 +244,29 @@
return false;
}
+ /**
+ * Determines if the file or non-empty directory exists on the device.
+ *
+ * @param deviceFilePath The absolute file path on device to check for existence.
+ * @return True if file/directory exists, False otherwise. If directory is empty, it will return
+ * False as well.
+ */
+ public boolean doesFileExist(String deviceFilePath) throws DeviceNotAvailableException {
+ String contentUri = createEscapedContentUri(deviceFilePath);
+ String queryContentCommand =
+ String.format(
+ "content query --user %d --uri %s", mDevice.getCurrentUser(), contentUri);
+
+ String listCommandResult = mDevice.executeShellCommand(queryContentCommand);
+
+ if (NO_RESULTS_STRING.equals(listCommandResult.trim())) {
+ // No file found.
+ return false;
+ }
+
+ return true;
+ }
+
/** Returns true if {@link CommandStatus} is successful and there is no error message. */
private boolean isSuccessful(CommandResult result) {
if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
diff --git a/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java b/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java
index e40e7b5..18cbe19 100644
--- a/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java
+++ b/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java
@@ -174,6 +174,7 @@
}
if (errorIdentifier != null) {
failure.setErrorIdentifier(errorIdentifier);
+ failure.setFailureStatus(errorIdentifier.status());
}
// Automatically populate the origin
Class<?> clazz = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass();
diff --git a/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java b/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
index caa5f1e..50084c4 100644
--- a/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
+++ b/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
@@ -47,7 +47,13 @@
CF_FETCH_ARTIFACT_TIME("cf_fetch_artifact_time_ms", false),
CF_GCE_CREATE_TIME("cf_gce_create_time_ms", false),
CF_LAUNCH_CVD_TIME("cf_launch_cvd_time_ms", false),
- CF_INSTANCE_COUNT("cf_instance_count", false);
+ CF_INSTANCE_COUNT("cf_instance_count", false),
+ CRASH_FAILURES("crash_failures", true),
+ UNCAUGHT_CRASH_FAILURES("uncaught_crash_failures", true),
+ TEST_CRASH_FAILURES("test_crash_failures", true),
+ UNCAUGHT_TEST_CRASH_FAILURES("uncaught_test_crash_failures", true),
+ DEVICE_RESET_COUNT("device_reset_count", true),
+ DEVICE_RESET_MODULES("device_reset_modules", true);
private final String mKeyName;
// Whether or not to add the value when the key is added again.
diff --git a/invocation_interfaces/com/android/tradefed/result/TestResult.java b/invocation_interfaces/com/android/tradefed/result/TestResult.java
index 06d47ad..1b26db6 100644
--- a/invocation_interfaces/com/android/tradefed/result/TestResult.java
+++ b/invocation_interfaces/com/android/tradefed/result/TestResult.java
@@ -208,6 +208,7 @@
int ignored = 0;
int incomplete = 0;
+ TestStatus lastStatus = null;
for (TestResult attempt : results) {
mergedResult.mProtoMetrics.putAll(attempt.getProtoMetrics());
mergedResult.mMetrics.putAll(attempt.getMetrics());
@@ -238,6 +239,7 @@
ignored++;
break;
}
+ lastStatus = attempt.getStatus();
}
switch (strategy) {
@@ -258,11 +260,17 @@
mergedResult.setStatus(TestStatus.INCOMPLETE);
}
} else {
- mergedResult.setStatus(TestStatus.FAILURE);
+ if (TestStatus.ASSUMPTION_FAILURE.equals(lastStatus)) {
+ mergedResult.setStatus(TestStatus.ASSUMPTION_FAILURE);
+ } else if (TestStatus.IGNORED.equals(lastStatus)) {
+ mergedResult.setStatus(TestStatus.IGNORED);
+ } else {
+ mergedResult.setStatus(TestStatus.FAILURE);
+ }
}
break;
default:
- // We keep a sane default of one failure is a failure that should be reported.
+ // We keep a default of one failure is a failure that should be reported.
if (fail > 0) {
mergedResult.setStatus(TestStatus.FAILURE);
} else {
diff --git a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
index a3fdc1e..3f35209 100644
--- a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
+++ b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
@@ -307,6 +307,10 @@
updateTestResult(test, TestStatus.ASSUMPTION_FAILURE, FailureDescription.create(trace));
}
+ public void testAssumptionFailure(TestDescription test, FailureDescription failure) {
+ updateTestResult(test, TestStatus.ASSUMPTION_FAILURE, failure);
+ }
+
public void testIgnored(TestDescription test) {
updateTestResult(test, TestStatus.IGNORED, null);
}
diff --git a/proto/monitoring/server/lab_resource.proto b/proto/monitoring/server/lab_resource.proto
new file mode 100644
index 0000000..5397abe
--- /dev/null
+++ b/proto/monitoring/server/lab_resource.proto
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2020 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.
+ */
+
+// The protobuf messages for lab host to export metadata and reosurce metrics.
+syntax = "proto3";
+
+package dual_home_lab.monitoring_agent.resource_monitoring;
+
+import "google/protobuf/timestamp.proto";
+
+option java_package = "com.google.dualhomelab.monitoringagent.resourcemonitoring";
+option java_multiple_files = true;
+option java_generic_services = true;
+
+// A tag-value pair message represents the metric value.
+// For example:
+// To represent device disk used percentage.
+// metric {
+// tag: "/data"
+// value: 20.5
+// }
+message Metric {
+ // A string tag associates to the value.
+ string tag = 1;
+ // A float value represents the resource value.
+ float value = 2;
+}
+
+// A message that describes the resource and its metrics.
+// For example:
+// To represent disk space usage values at certain moment.
+// resource {
+// resource_name: "disk_space"
+// resource_instance: "/data"
+// timestamp {
+// seconds: 1589342214
+// }
+// metric: {
+// tag: "avail"
+// value: 20.5
+// }
+// metric: {
+// tag: "used"
+// value: 18.7
+// }
+// metric: {
+// tag: "reserved for root"
+// value: 16.2
+// }
+// }
+message Resource {
+ // A string resource name. ex. "cpu", "memory", "disk_space".
+ string resource_name = 1;
+ // A string reperesent the sub resource name.
+ string resource_instance = 2;
+ // The Metric describe the host or device resource usages.
+ repeated Metric metric = 3;
+ // The collecting timestamp.
+ google.protobuf.Timestamp timestamp = 4;
+}
+
+// A name-value message to represent the metadata attribute.
+// For example:
+// To represent the run target.
+// attribute {
+// name: "run_target"
+// value: "atom-userdebug"
+// }
+// To reperent the pools.
+// attribute {
+// name: "pool"
+// value: "asit"
+// }
+// attribute {
+// name: "pool"
+// value: "apct"
+// }
+message Attribute {
+ string name = 1;
+ string value = 2;
+}
+
+// A message that describes the device state and resource usages.
+// For example:
+// To represent a monitored host
+// host {
+// identifier: {
+// key: "lab_name"
+// value: "us-mtv43"
+// }
+// identifier: {
+// key: "host_name"
+// value: "foo.bar.com"
+// }
+// identifier: {
+// key: "test_harness"
+// value: "tradefed"
+// }
+// attribute: {... check the attribute example above ...}
+// resource: {... check the resource example abobe ...}
+// }
+// To represent a monitored device
+// device {
+// identifier: {
+// key: "device_serial"
+// value: "VVEG-GIDSAN"
+// }
+// attribute: {... check the attribute example above ...}
+// resource: {... check the resource example abobe ...}
+// }
+message MonitoredEntity {
+ // The string map that helps identify the monitored entity
+ map<string, string> identifier = 1;
+ // The attribute messages that describe the device metadata.
+ repeated Attribute attribute = 2;
+ // The resource messages that describe the device state and resource metrics.
+ repeated Resource resource = 3;
+}
+
+// A message that describe the state and resource usages for a lab host and its
+// connected devices.
+message LabResource {
+ MonitoredEntity host = 1;
+ repeated MonitoredEntity device = 2;
+}
+
+// The request message to query the LabResource.
+message LabResourceRequest {}
+
+// The service which is intend to export device metrics and metadata to the host
+// monitoring agent. The host monitoring agent is responsible for
+// collecting host/device metrics and exporting the metrics to user defined
+// cloud PubSub topics.
+service LabResourceService {
+ // Queries lab resource message.
+ rpc GetLabResource(LabResourceRequest) returns (LabResource) {
+ // The http equivalent is curl http://ADDRESS/v1/lab_resource_message
+ // (Assuming your service is hosted at the given 'ADDRESS')
+ }
+}
diff --git a/res/config/template/preparers/dsu-preparer.xml b/res/config/template/preparers/dsu-preparer.xml
index 565b609..7e716ce 100644
--- a/res/config/template/preparers/dsu-preparer.xml
+++ b/res/config/template/preparers/dsu-preparer.xml
@@ -1,5 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2020 Google Inc. All Rights Reserved -->
+<!-- Copyright (C) 2020 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.
+-->
<configuration description="Common template preparer for dynamic system upgrade (DSU)." >
+ <template-include name="base-preparer" default="empty" />
<target_preparer class="com.android.tradefed.targetprep.DynamicSystemPreparer" />
+ <!-- Can use template/preparers/gki-device-flash-preparer for extra GKI flash-->
+ <template-include name="gki-preparer" default="empty" />
</configuration>
diff --git a/res/config/template/preparers/fastboot-flash-preparer.xml b/res/config/template/preparers/fastboot-flash-preparer.xml
new file mode 100644
index 0000000..74541de
--- /dev/null
+++ b/res/config/template/preparers/fastboot-flash-preparer.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Common template for flashing the device." >
+ <target_preparer class="com.android.tradefed.targetprep.FastbootUpdateBootstrapPreparer" />
+</configuration>
diff --git a/res/config/template/preparers/gki-device-flash-preparer.xml b/res/config/template/preparers/gki-device-flash-preparer.xml
index dfeffa9..59758ba 100644
--- a/res/config/template/preparers/gki-device-flash-preparer.xml
+++ b/res/config/template/preparers/gki-device-flash-preparer.xml
@@ -1,5 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2020 Google Inc. All Rights Reserved -->
+<!-- Copyright (C) 2020 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.
+-->
<configuration description="Common template for flashing the device with GKI image." >
<target_preparer class="com.android.tradefed.targetprep.GkiDeviceFlashPreparer" />
</configuration>
diff --git a/res/config/template/preparers/gsi-device-flash-preparer.xml b/res/config/template/preparers/gsi-device-flash-preparer.xml
index 6c86c49..6121ea5 100644
--- a/res/config/template/preparers/gsi-device-flash-preparer.xml
+++ b/res/config/template/preparers/gsi-device-flash-preparer.xml
@@ -1,5 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2020 Google Inc. All Rights Reserved -->
+<!-- Copyright (C) 2020 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.
+-->
<configuration description="Common template for flashing the device with GSI image." >
+ <template-include name="base-preparer" default="empty" />
<target_preparer class="com.android.tradefed.targetprep.GsiDeviceFlashPreparer" />
</configuration>
diff --git a/res/suite/allowed-preparers.txt b/res/suite/allowed-preparers.txt
new file mode 100644
index 0000000..48ed998
--- /dev/null
+++ b/res/suite/allowed-preparers.txt
@@ -0,0 +1,4 @@
+com.android.tradefed.targetprep.CrashCollector
+com.android.tradefed.targetprep.DeviceCleaner
+com.android.tradefed.targetprep.RootTargetPreparer
+com.android.tradefed.targetprep.WifiPreparer
\ No newline at end of file
diff --git a/src/com/android/tradefed/build/BuildRetrievalError.java b/src/com/android/tradefed/build/BuildRetrievalError.java
index 56e6921..fc5e4ce 100644
--- a/src/com/android/tradefed/build/BuildRetrievalError.java
+++ b/src/com/android/tradefed/build/BuildRetrievalError.java
@@ -16,8 +16,10 @@
package com.android.tradefed.build;
import com.android.tradefed.error.HarnessException;
+import com.android.tradefed.error.IHarnessException;
import com.android.tradefed.result.error.ErrorIdentifier;
+
/** A fatal error occurred while retrieving the build for testing. */
public class BuildRetrievalError extends HarnessException {
@@ -92,6 +94,9 @@
if (build != null) {
mBuildInfo = build;
}
+ if (cause instanceof IHarnessException) {
+ setCallerClass(((IHarnessException) cause).getOrigin());
+ }
}
/**
diff --git a/src/com/android/tradefed/build/DependenciesResolver.java b/src/com/android/tradefed/build/DependenciesResolver.java
index 66ccd89..f55d098 100644
--- a/src/com/android/tradefed/build/DependenciesResolver.java
+++ b/src/com/android/tradefed/build/DependenciesResolver.java
@@ -59,7 +59,7 @@
@Option(name = "protocol")
private String mProtocol = null;
- @Option(name = "use-build-api ")
+ @Option(name = "use-build-api")
private boolean mUseBuildApi = true;
private File mTestsDir;
diff --git a/src/com/android/tradefed/build/FileDownloadCache.java b/src/com/android/tradefed/build/FileDownloadCache.java
index 357eccc..7cbd297 100644
--- a/src/com/android/tradefed/build/FileDownloadCache.java
+++ b/src/com/android/tradefed/build/FileDownloadCache.java
@@ -401,7 +401,7 @@
@VisibleForTesting
File copyFile(String remotePath, File cachedFile, File destFile) throws BuildRetrievalError {
- // attempt to create a local copy of cached file with sane name
+ // attempt to create a local copy of cached file with meaningful name
File hardlinkFile = destFile;
try {
if (hardlinkFile == null) {
diff --git a/src/com/android/tradefed/cluster/ClusterClient.java b/src/com/android/tradefed/cluster/ClusterClient.java
index b9d7fd2..7d006a5 100644
--- a/src/com/android/tradefed/cluster/ClusterClient.java
+++ b/src/com/android/tradefed/cluster/ClusterClient.java
@@ -214,6 +214,11 @@
@Override
public ClusterCommand.State getCommandState(String requestId, String commandId) {
+ return getCommandStatus(requestId, commandId).getState();
+ }
+
+ @Override
+ public ClusterCommandStatus getCommandStatus(String requestId, String commandId) {
try {
HttpResponse response =
getApiHelper()
@@ -223,12 +228,14 @@
new HashMap<>(),
null);
String content = StreamUtil.getStringFromStream(response.getContent());
- String value = new JSONObject(content).getString("state");
- return ClusterCommand.State.valueOf(value);
+ JSONObject jsonContent = new JSONObject(content);
+ String value = jsonContent.getString("state");
+ String cancelReason = jsonContent.optString("cancel_reason", "");
+ return new ClusterCommandStatus(ClusterCommand.State.valueOf(value), cancelReason);
} catch (IOException | JSONException | IllegalArgumentException e) {
CLog.w("Failed to get state of request %s command %s", requestId, commandId);
CLog.e(e);
- return ClusterCommand.State.UNKNOWN;
+ return new ClusterCommandStatus(ClusterCommand.State.UNKNOWN, "");
}
}
diff --git a/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java b/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java
index 6e3ffbb..e50699c 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java
@@ -192,7 +192,9 @@
envVars.put("TF_WORK_DIR", mWorkDir.getAbsolutePath());
envVars.putAll(mTestEnvironment.getEnvVars());
envVars.putAll(mTestContext.getEnvVars());
+
for (String serial : mCommand.getTargetDeviceSerials()) {
+ serial = ClusterHostUtil.getLocalDeviceSerial(serial);
IDeviceConfiguration device =
new DeviceConfigurationHolder(String.format("TF_DEVICE_%d", index++));
device.getDeviceRequirements().setSerial(serial);
@@ -203,6 +205,11 @@
}
deviceConfigs.get(0).addSpecificConfig(new ClusterBuildProvider());
config.setDeviceConfigList(deviceConfigs);
+ // Perform target preparation in parallel with an unlimited timeout
+ // TODO(b/166455187): Consider making parallel setup options configurable
+ config.injectOptionValue("parallel-setup", "true");
+ config.injectOptionValue("parallel-setup-timeout", "0");
+
config.setTest(new ClusterCommandLauncher());
config.setLogSaver(new ClusterLogSaver());
// TODO(b/135636270): return log path to TFC instead of relying on a specific filename
diff --git a/src/com/android/tradefed/cluster/ClusterCommandEvent.java b/src/com/android/tradefed/cluster/ClusterCommandEvent.java
index 725e48e..46db15c 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandEvent.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandEvent.java
@@ -38,6 +38,7 @@
public static final String DATA_KEY_PASSED_TEST_COUNT = "passed_test_count";
public static final String DATA_KEY_FAILED_TEST_RUN_COUNT = "failed_test_run_count";
public static final String DATA_KEY_LOST_DEVICE_DETECTED = "device_lost_detected";
+ public static final String DATA_KEY_SUBPROCESS_COMMAND_ERROR = "subprocess_command_error";
// Maximum size of an individual data string value.
public static final int MAX_DATA_STRING_SIZE = 4095;
diff --git a/src/com/android/tradefed/cluster/ClusterCommandLauncher.java b/src/com/android/tradefed/cluster/ClusterCommandLauncher.java
index 63e34a3..0992202 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandLauncher.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandLauncher.java
@@ -26,6 +26,7 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.testtype.IInvocationContextReceiver;
@@ -41,12 +42,14 @@
import com.android.tradefed.util.StreamUtil;
import com.android.tradefed.util.StringEscapeUtils;
import com.android.tradefed.util.StringUtil;
+import com.android.tradefed.util.SubprocessEventHelper.InvocationFailedEventInfo;
import com.android.tradefed.util.SubprocessTestResultsParser;
import com.android.tradefed.util.SystemUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -133,32 +136,10 @@
}
@Override
- public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
- // Get an expanded TF_PATH value.
- String tfPath = getEnvVar(TF_PATH, System.getProperty(TF_JAR_DIR));
- if (tfPath == null) {
- throw new RuntimeException("cannot find TF path!");
- }
-
- // Construct a Java class path based on TF_PATH value.
- // This expects TF_PATH to be a colon(:) separated list of paths where each path
- // points to a specific jar file or folder.
- // (example: path/to/tradefed.jar:path/to/tradefed/folder:...)
- final Set<String> jars = new LinkedHashSet<>();
- for (final String path : tfPath.split(":")) {
- final File jarFile = new File(path);
- if (!jarFile.exists()) {
- CLog.w("TF_PATH %s doesn't exist; ignoring", path);
- continue;
- }
- if (jarFile.isFile()) {
- jars.add(jarFile.getAbsolutePath());
- } else {
- jars.add(new File(path, "*").getAbsolutePath());
- }
- }
-
- IRunUtil runUtil = getRunUtil();
+ public void run(TestInformation testInfo, ITestInvocationListener listener)
+ throws DeviceNotAvailableException {
+ // Prepare a IRunUtil instance for running subprocesses.
+ final IRunUtil runUtil = getRunUtil();
runUtil.setWorkingDir(mRootDir);
// clear the TF_GLOBAL_CONFIG env, so another tradefed will not reuse the global config file
runUtil.unsetEnvVariable(GlobalConfiguration.GLOBAL_CONFIG_VARIABLE);
@@ -171,11 +152,86 @@
logDir.mkdirs();
File stdoutFile = new File(logDir, "stdout.txt");
File stderrFile = new File(logDir, "stderr.txt");
- FileIdleMonitor monitor = createFileMonitor(stdoutFile, stderrFile);
+ // Run setup scripts.
+ runSetupScripts(runUtil, stdoutFile, stderrFile);
+
+ FileIdleMonitor monitor = createFileMonitor(stdoutFile, stderrFile);
SubprocessTestResultsParser subprocessEventParser = null;
try (FileOutputStream stdout = new FileOutputStream(stdoutFile);
FileOutputStream stderr = new FileOutputStream(stderrFile)) {
+
+ String classpath = buildJavaClasspath();
+
+ // TODO(b/129111645): use proto reporting if a test suite supports it.
+ if (mUseSubprocessReporting) {
+ subprocessEventParser =
+ createSubprocessTestResultsParser(listener, true, mInvocationContext);
+ final String port = Integer.toString(subprocessEventParser.getSocketServerPort());
+ // Create injection jar for subprocess result reporter, which is used
+ // for pre-R xTS. The created jar is put in front position of the class path to
+ // override class with the same name.
+ final SubprocessReportingHelper mHelper =
+ new SubprocessReportingHelper(mCommandLine, classpath, testWorkDir, port);
+ final File subprocessReporterJar = mHelper.buildSubprocessReporterJar();
+ classpath =
+ String.format("%s:%s", subprocessReporterJar.getAbsolutePath(), classpath);
+ }
+
+ List<String> javaCommandArgs = buildJavaCommandArgs(classpath, mCommandLine);
+ CLog.i("Running a command line: %s", mCommandLine);
+ CLog.i("args = %s", javaCommandArgs);
+ CLog.i("test working directory = %s", testWorkDir);
+
+ monitor.start();
+ runUtil.setWorkingDir(testWorkDir);
+ CommandResult result =
+ runUtil.runTimedCmd(
+ mConfiguration.getCommandOptions().getInvocationTimeout(),
+ stdout,
+ stderr,
+ javaCommandArgs.toArray(new String[javaCommandArgs.size()]));
+ if (!result.getStatus().equals(CommandStatus.SUCCESS)) {
+ String error = null;
+ Throwable cause = null;
+ if (result.getStatus().equals(CommandStatus.TIMED_OUT)) {
+ error =
+ String.format(
+ "Command timed out after %sms",
+ mConfiguration.getCommandOptions().getInvocationTimeout());
+ } else {
+ error =
+ String.format(
+ "Command finished unsuccessfully: status=%s, exit_code=%s",
+ result.getStatus(), result.getExitCode());
+ InvocationFailedEventInfo errorInfo =
+ subprocessEventParser.getReportedInvocationFailedEventInfo();
+ if (errorInfo != null) {
+ cause = errorInfo.mCause;
+ } else {
+ cause = new Throwable(FileUtil.readStringFromFile(stderrFile));
+ }
+ }
+ throw new SubprocessCommandException(error, cause);
+ }
+ CLog.i("Successfully ran a command");
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ monitor.stop();
+ if (subprocessEventParser != null) {
+ subprocessEventParser.joinReceiver(
+ MAX_EVENT_RECEIVER_WAIT_TIME.toMillis(), /* wait for connection */ false);
+ StreamUtil.close(subprocessEventParser);
+ }
+ }
+ }
+
+ private void runSetupScripts(
+ final IRunUtil runUtil, final File stdoutFile, final File stderrFile) {
+ try (FileOutputStream stdout = new FileOutputStream(stdoutFile);
+ FileOutputStream stderr = new FileOutputStream(stderrFile)) {
long timeout = mScriptTimeout;
long startTime = System.currentTimeMillis();
for (String script : mSetupScripts) {
@@ -203,74 +259,42 @@
String.format("Setup scripts failed to run in %sms", mScriptTimeout));
}
}
-
- String classpath = ArrayUtil.join(":", jars);
- String commandLine = mCommandLine;
- if (classpath.isEmpty()) {
- throw new RuntimeException(
- String.format("cannot find any TF jars from %s!", tfPath));
- }
-
- if (mOriginalCommandLine != null && !mOriginalCommandLine.equals(commandLine)) {
- // Make sure a wrapper XML of the original command is available because retries
- // try to run original commands in Q+. If the original command was run with
- // subprocess reporting, a recorded command would be one with .xml suffix.
- new SubprocessConfigBuilder()
- .setWorkingDir(testWorkDir)
- .setOriginalConfig(
- QuotationAwareTokenizer.tokenizeLine(mOriginalCommandLine)[0])
- .build();
- }
- if (mUseSubprocessReporting) {
- SubprocessReportingHelper mHelper = new SubprocessReportingHelper();
- // Create standalone jar for subprocess result reporter, which is used
- // for pre-O cts. The created jar is put in front position of the class path to
- // override class with the same name.
- classpath =
- String.format(
- "%s:%s",
- mHelper.createSubprocessReporterJar(mRootDir).getAbsolutePath(),
- classpath);
- subprocessEventParser =
- createSubprocessTestResultsParser(listener, true, mInvocationContext);
- String port = Integer.toString(subprocessEventParser.getSocketServerPort());
- commandLine = mHelper.buildNewCommandConfig(commandLine, port, testWorkDir);
- }
-
- List<String> javaCommandArgs = buildJavaCommandArgs(classpath, commandLine);
- CLog.i("Running a command line: %s", commandLine);
- CLog.i("args = %s", javaCommandArgs);
- CLog.i("test working directory = %s", testWorkDir);
-
- monitor.start();
- runUtil.setWorkingDir(testWorkDir);
- CommandResult result =
- runUtil.runTimedCmd(
- mConfiguration.getCommandOptions().getInvocationTimeout(),
- stdout,
- stderr,
- javaCommandArgs.toArray(new String[javaCommandArgs.size()]));
- if (!result.getStatus().equals(CommandStatus.SUCCESS)) {
- String error = null;
- if (result.getStatus().equals(CommandStatus.TIMED_OUT)) {
- error = "timeout";
- } else {
- error = FileUtil.readStringFromFile(stderrFile);
- }
- throw new RuntimeException(String.format("Command failed to run: %s", error));
- }
- CLog.i("Successfully ran a command");
-
} catch (IOException e) {
- throw new RuntimeException(e);
- } finally {
- monitor.stop();
- if (subprocessEventParser != null) {
- subprocessEventParser.joinReceiver(
- MAX_EVENT_RECEIVER_WAIT_TIME.toMillis(), /* wait for connection */ false);
- StreamUtil.close(subprocessEventParser);
+ throw new UncheckedIOException("Error running setup scripts", e);
+ }
+ }
+
+ private String buildJavaClasspath() {
+ // Get an expanded TF_PATH value.
+ final String tfPath = getEnvVar(TF_PATH, System.getProperty(TF_JAR_DIR));
+ if (tfPath == null) {
+ throw new RuntimeException("cannot find TF path!");
+ }
+
+ // Construct a Java class path based on TF_PATH value.
+ // This expects TF_PATH to be a colon(:) separated list of paths where each path
+ // points to a specific jar file or folder.
+ // (example: path/to/tradefed.jar:path/to/tradefed/folder:...)
+ // TODO(b/162473907): deprecate TF_PATH.
+ final Set<String> jars = new LinkedHashSet<>();
+ for (final String path : tfPath.split(":")) {
+ final File jarFile = new File(path);
+ if (!jarFile.exists()) {
+ CLog.w("TF_PATH %s doesn't exist; ignoring", path);
+ continue;
+ }
+ if (jarFile.isFile()) {
+ jars.add(jarFile.getAbsolutePath());
+ } else {
+ // Add a folder path to the classpath to handle class file directories.
+ jars.add(jarFile.getAbsolutePath() + "/");
+ jars.add(new File(path, "*").getAbsolutePath());
}
}
+ if (jars.isEmpty()) {
+ throw new RuntimeException(String.format("cannot find any TF jars from %s!", tfPath));
+ }
+ return String.join(":", jars);
}
/** Build a shell command line to invoke a TF process. */
diff --git a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
index 9702419..075b3e8 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
@@ -145,6 +145,7 @@
private String mSummary;
private Set<String> processedSummaries = new HashSet<>();
private String mError;
+ private String mSubprocessCommandError;
private File mWorkDir;
private InvocationStatus mInvocationStatus;
@@ -261,6 +262,10 @@
super.invocationFailed(cause);
mError = StreamUtil.getStackTrace(cause);
+ if (cause instanceof SubprocessCommandException && cause.getCause() != null) {
+ // The inner exception holds an exception stack trace from a subprocess.
+ mSubprocessCommandError = cause.getCause().getMessage();
+ }
}
/** {@inheritDoc} */
@@ -272,6 +277,9 @@
createEventBuilder()
.setType(ClusterCommandEvent.Type.InvocationEnded)
.setData(ClusterCommandEvent.DATA_KEY_ERROR, mError)
+ .setData(
+ ClusterCommandEvent.DATA_KEY_SUBPROCESS_COMMAND_ERROR,
+ mSubprocessCommandError)
.build();
getClusterClient().getCommandEventUploader().postEvent(event);
getClusterClient().getCommandEventUploader().flush();
@@ -318,6 +326,9 @@
.setType(ClusterCommandEvent.Type.InvocationCompleted)
.setInvocationStatus(mInvocationStatus)
.setData(ClusterCommandEvent.DATA_KEY_ERROR, mError)
+ .setData(
+ ClusterCommandEvent.DATA_KEY_SUBPROCESS_COMMAND_ERROR,
+ mSubprocessCommandError)
.setData(ClusterCommandEvent.DATA_KEY_SUMMARY, mSummary)
.setData(
ClusterCommandEvent.DATA_KEY_FETCH_BUILD_TIME_MILLIS,
@@ -380,22 +391,22 @@
@Override
public void run() {
try {
- // check cluster command's status
+ // Check cluster command's status.
if (getClusterOptions().checkCommandState()) {
- ClusterCommand.State status =
+ ClusterCommandStatus commandStatus =
getClusterClient()
- .getCommandState(
+ .getCommandStatus(
mCommandTask.getRequestId(),
mCommandTask.getCommandId());
- if (ClusterCommand.State.CANCELED.equals(status)) {
- // TODO: retrieve cancel reason from TFC.
+ if (ClusterCommand.State.CANCELED.equals(commandStatus.getState())) {
String cause =
String.format(
"The cluster client %s has marked command "
- + "(requestId=%s, commandId=%s) canceled",
+ + "(requestId=%s, commandId=%s) canceled with reason: %s",
getClusterClient().getClass().getSimpleName(),
mCommandTask.getRequestId(),
- mCommandTask.getCommandId());
+ mCommandTask.getCommandId(),
+ commandStatus.getCancelReason());
CLog.w("Stop invocation due to: %s", cause);
Optional.ofNullable(getInvocationContext())
.map(IInvocationContext::getInvocationId)
@@ -497,7 +508,6 @@
String runTarget =
ClusterHostUtil.getRunTarget(
device, runTargetFormat, getClusterOptions().getDeviceTag());
- CLog.d("%s is available", runTarget);
devices.put(runTarget, device);
}
return devices;
@@ -605,6 +615,9 @@
ClusterCommandEvent.createEventBuilder(commandTask)
.setHostName(ClusterHostUtil.getHostName())
.setType(ClusterCommandEvent.Type.AllocationFailed)
+ .setData(
+ ClusterCommandEvent.DATA_KEY_ERROR,
+ StreamUtil.getStackTrace(e))
.build());
eventUploader.flush();
} catch (ConfigurationException | IOException | JSONException e) {
@@ -616,7 +629,9 @@
ClusterCommandEvent.createEventBuilder(commandTask)
.setHostName(ClusterHostUtil.getHostName())
.setType(ClusterCommandEvent.Type.ConfigurationError)
- .setData(ClusterCommandEvent.DATA_KEY_ERROR, e.toString())
+ .setData(
+ ClusterCommandEvent.DATA_KEY_ERROR,
+ StreamUtil.getStackTrace(e))
.build());
eventUploader.flush();
}
@@ -638,7 +653,7 @@
if (commandTask.getTargetDeviceSerials() != null) {
for (String serial : commandTask.getTargetDeviceSerials()) {
cmdLine += " --serial ";
- cmdLine += serial;
+ cmdLine += ClusterHostUtil.getLocalDeviceSerial(serial);
}
}
CLog.i("executing cluster command: [%s] %s", commandTask.getTaskId(), cmdLine);
@@ -692,8 +707,7 @@
*/
protected boolean dryRunCommand(final InvocationEventHandler handler, String[] args)
throws ConfigurationException {
- IConfiguration config =
- getConfigFactory().createConfigurationFromArgs(args, null, getKeyStoreClient());
+ IConfiguration config = createConfiguration(args);
if (config.getCommandOptions().isDryRunMode()) {
IInvocationContext context = new InvocationContext();
context.addDeviceBuildInfo("stub", new BuildInfo());
diff --git a/src/com/android/tradefed/cluster/ClusterCommandStatus.java b/src/com/android/tradefed/cluster/ClusterCommandStatus.java
new file mode 100644
index 0000000..ef95614
--- /dev/null
+++ b/src/com/android/tradefed/cluster/ClusterCommandStatus.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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.cluster;
+
+/** A class that represents the state and the cancel reason for a command from TF Cluster. */
+public class ClusterCommandStatus {
+ private final ClusterCommand.State mState;
+ private final String mCancelReason;
+
+ /**
+ * Construct
+ *
+ * @param state state of the command.
+ * @param cancelReason cancel reason of the command (if canceled).
+ */
+ public ClusterCommandStatus(ClusterCommand.State state, String cancelReason) {
+ mState = state;
+ mCancelReason = cancelReason;
+ }
+
+ public ClusterCommand.State getState() {
+ return mState;
+ }
+
+ public String getCancelReason() {
+ return mCancelReason;
+ }
+}
diff --git a/src/com/android/tradefed/cluster/ClusterDeviceInfo.java b/src/com/android/tradefed/cluster/ClusterDeviceInfo.java
index 23c39eb..0fcf346 100644
--- a/src/com/android/tradefed/cluster/ClusterDeviceInfo.java
+++ b/src/com/android/tradefed/cluster/ClusterDeviceInfo.java
@@ -82,7 +82,7 @@
*/
public JSONObject toJSON() throws JSONException {
final JSONObject json = new JSONObject();
- json.put("device_serial", mDeviceDescriptor.getSerial());
+ json.put("device_serial", ClusterHostUtil.getUniqueDeviceSerial(mDeviceDescriptor));
json.put("run_target", mRunTarget);
json.put("build_id", mDeviceDescriptor.getBuildId());
json.put("product", mDeviceDescriptor.getProduct());
diff --git a/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java b/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java
index 0ca1be1..c8f0fa5 100644
--- a/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java
+++ b/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java
@@ -108,6 +108,12 @@
.setHostName(ClusterHostUtil.getHostName())
.setTfVersion(ClusterHostUtil.getTfVersion())
.setData(getAdditionalHostInfo())
+ .setData(
+ ClusterHostEvent.HOST_IP_KEY,
+ ClusterHostUtil.getHostIpAddress())
+ .setData(
+ ClusterHostEvent.LABEL_KEY,
+ String.join(",", getClusterOptions().getLabels()))
.setClusterId(getClusterOptions().getClusterId())
.setNextClusterIds(getClusterOptions().getNextClusterIds())
.setLabName(getClusterOptions().getLabName());
diff --git a/src/com/android/tradefed/cluster/ClusterHostEvent.java b/src/com/android/tradefed/cluster/ClusterHostEvent.java
index 8a56e93..b3bb430 100644
--- a/src/com/android/tradefed/cluster/ClusterHostEvent.java
+++ b/src/com/android/tradefed/cluster/ClusterHostEvent.java
@@ -37,6 +37,8 @@
private Map<String, String> mData = new HashMap<>();
private String mLabName;
public static final String EVENT_QUEUE = "host-event-queue";
+ public static final String LABEL_KEY = "label";
+ public static final String HOST_IP_KEY = "host_ip";
/** Enums of the different types of host events. */
public enum HostEventType {
diff --git a/src/com/android/tradefed/cluster/ClusterHostUtil.java b/src/com/android/tradefed/cluster/ClusterHostUtil.java
index b68c9c1..5bc6209 100644
--- a/src/com/android/tradefed/cluster/ClusterHostUtil.java
+++ b/src/com/android/tradefed/cluster/ClusterHostUtil.java
@@ -27,36 +27,137 @@
import com.google.common.net.InetAddresses;
import com.google.common.primitives.Longs;
+import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
import java.util.Map;
+import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+
/** Static util functions for TF Cluster to get global config instances, host information, etc. */
public class ClusterHostUtil {
private static String sHostName = null;
+ private static String sHostIpAddress = null;
+
static final String DEFAULT_TF_VERSION = "(unknown)";
+ static final String EMULATOR_SERIAL_PREFIX = "emulator-";
+ static final String NULL_DEVICE_SERIAL_PLACEHOLDER = "(no device serial)";
+ static final String UNKNOWN = "UNKNOWN";
private static long sTfStartTime = getCurrentTimeMillis();
/**
* Gets the hostname.
*
- * @return the hostname or null if we were unable to fetch it
+ * <p>1. Try to get hostname from InetAddress. 2. If fail, try to get hostname from HOSTNAME
+ * env. 3. If not set, generate a unique hostname.
+ *
+ * @return the hostname or null if we were unable to fetch it.
*/
public static String getHostName() {
- if (sHostName == null) {
+ if (sHostName != null) {
+ return sHostName;
+ }
+ try {
+ sHostName = InetAddress.getLocalHost().getHostName();
+ return sHostName;
+ } catch (UnknownHostException e) {
+ CLog.w("Failed to get hostname from InetAddress: %s", e);
+ }
+ CLog.i("Get hostname from HOSTNAME env.");
+ sHostName = System.getenv("HOSTNAME");
+ if (!Strings.isNullOrEmpty(sHostName)) {
+ return sHostName;
+ }
+ sHostName = "unknown-" + UUID.randomUUID().toString();
+ CLog.i("No HOSTNAME env set. Generate hostname: %s.", sHostName);
+ return sHostName;
+ }
+
+ /**
+ * Returns a unique device serial for a device.
+ *
+ * <p>Non-physical devices (e.g. emulator) have pseudo serials which are not unique across
+ * hosts. This method prefixes those with a hostname to make them unique.
+ *
+ * @param device a device descriptor.
+ * @return a unique device serial.
+ */
+ public static String getUniqueDeviceSerial(DeviceDescriptor device) {
+ String serial = device.getSerial();
+ if (Strings.isNullOrEmpty(serial)
+ || device.isStubDevice()
+ || serial.startsWith(EMULATOR_SERIAL_PREFIX)) {
+ if (Strings.isNullOrEmpty(serial)) {
+ serial = NULL_DEVICE_SERIAL_PLACEHOLDER;
+ }
+ serial = String.format("%s:%s", getHostName(), serial);
+ }
+ return serial;
+ }
+
+ /**
+ * Returns a local device serial for a given unique device serial.
+ *
+ * <p>TFC sends down unique device serials for non-physical devices which TF does not
+ * understand. This method converts them back to local device serials.
+ *
+ * @param serial a unique device serial from TFC.
+ * @return a local device serial.
+ */
+ public static String getLocalDeviceSerial(String serial) {
+ String prefix = getHostName() + ":";
+ if (serial.startsWith(prefix)) {
+ return serial.substring(prefix.length());
+ }
+ return serial;
+ }
+ /**
+ * Gets the IP address.
+ *
+ * @return the IPV4 address String or "UNKNOWN" if we were unable to fetch it.
+ */
+ public static String getHostIpAddress() {
+ if (sHostIpAddress == null) {
+ List<InetAddress> addresses = new ArrayList<>();
try {
- sHostName = InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException e) {
- CLog.w("failed to get hostname: %s", e);
+ Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+ if (interfaces == null) {
+ return UNKNOWN;
+ }
+ for (NetworkInterface networkInterface : Collections.list(interfaces)) {
+ if (!networkInterface.isUp() || networkInterface.isLoopback()) {
+ continue;
+ }
+ for (InetAddress address :
+ Collections.list(networkInterface.getInetAddresses())) {
+ if (address.isLinkLocalAddress()
+ || address.isLoopbackAddress()
+ || address instanceof Inet6Address) {
+ continue;
+ }
+ addresses.add(address);
+ }
+ }
+ } catch (SocketException e) {
+ CLog.w(e);
+ }
+ if (!addresses.isEmpty()) {
+ sHostIpAddress = addresses.get(0).getHostAddress();
}
}
- return sHostName;
+ return sHostIpAddress == null ? UNKNOWN : sHostIpAddress;
}
/**
@@ -125,7 +226,7 @@
txt = device.getDeviceClass();
break;
case "SERIAL":
- txt = device.getSerial();
+ txt = getUniqueDeviceSerial(device);
break;
case "TAG":
if (deviceTags == null || deviceTags.isEmpty()) {
diff --git a/src/com/android/tradefed/cluster/IClusterClient.java b/src/com/android/tradefed/cluster/IClusterClient.java
index e803d7a..d232af0 100644
--- a/src/com/android/tradefed/cluster/IClusterClient.java
+++ b/src/com/android/tradefed/cluster/IClusterClient.java
@@ -90,6 +90,20 @@
throws IOException, JSONException;
/**
+ * Get the command status of a cluster command (the state and the cancel reason if canceled).
+ *
+ * @param requestId cluster request ID
+ * @param commandId cluster command ID
+ * @return a ClusterCommandStatus that represents the state and the cancel reason if the command
+ * is canceled. The state is {@link ClusterCommand.State#UNKNOWN} if it could not be
+ * determined.
+ */
+ public default ClusterCommandStatus getCommandStatus(String requestId, String commandId) {
+ ClusterCommand.State state = getCommandState(requestId, commandId);
+ return new ClusterCommandStatus(state, "");
+ }
+
+ /**
* Determine the state of a cluster command.
*
* @param requestId cluster request ID
diff --git a/src/com/android/tradefed/cluster/SubprocessCommandException.java b/src/com/android/tradefed/cluster/SubprocessCommandException.java
new file mode 100644
index 0000000..3be5dc9
--- /dev/null
+++ b/src/com/android/tradefed/cluster/SubprocessCommandException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2020 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.cluster;
+
+/** A subprocess command failed to run. */
+public class SubprocessCommandException extends RuntimeException {
+
+ public SubprocessCommandException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java b/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java
index 155b80b..6952542 100644
--- a/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java
+++ b/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java
@@ -18,12 +18,32 @@
import com.android.tradefed.config.Configuration;
import com.android.tradefed.config.ConfigurationUtil;
import com.android.tradefed.result.LegacySubprocessResultsReporter;
+import com.android.tradefed.util.FileUtil;
-import org.kxml2.io.KXmlSerializer;
+import com.google.common.base.Strings;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.PrintWriter;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
/**
* Build a wrapper TF config XML for an existing TF config.
@@ -31,19 +51,23 @@
* <p>A wrapper XML allows to enable subprocess reporting on an existing TF config.
*/
public class SubprocessConfigBuilder {
- private static final String INCLUDE_NAME = "include";
private static final String REPORTER_CLASS = LegacySubprocessResultsReporter.class.getName();
private static final String OPTION_KEY = "subprocess-report-port";
- private static final String CONFIG_DESCRIPTION = "Cluster Command Launcher config";
+ private String mClasspath;
- private File mWorkdir;
+ private File mWorkDir;
private String mOriginalConfig;
private String mPort;
+ public SubprocessConfigBuilder setClasspath(String classpath) {
+ mClasspath = classpath;
+ return this;
+ }
+
public SubprocessConfigBuilder setWorkingDir(File dir) {
- mWorkdir = dir;
+ mWorkDir = dir;
return this;
}
@@ -66,43 +90,79 @@
}
public File build() throws IOException {
- // Make a new config name based on the original config name to make it possible to find
- // out the original command line from a modified one.
- // FIXME: Find a better way to preserve the original command line.
- String configName = createConfigName(mOriginalConfig);
- // mOriginalConfig is from another test suite, so its content is hard to know at this
- // time. So it doesn't load mOriginalConfig as IConfiguration and add additional config.
- // Instead, it creates a wrapper config including mOriginalConfig.
- File f = new File(mWorkdir, configName);
- PrintWriter writer = new PrintWriter(f);
- KXmlSerializer serializer = new KXmlSerializer();
- serializer.setOutput(writer);
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- serializer.startDocument("UTF-8", null);
- serializer.startTag(null, ConfigurationUtil.CONFIGURATION_NAME);
- serializer.attribute(
- null, Configuration.CONFIGURATION_DESCRIPTION_TYPE_NAME, CONFIG_DESCRIPTION);
-
- serializer.startTag(null, INCLUDE_NAME);
- serializer.attribute(null, ConfigurationUtil.NAME_NAME, mOriginalConfig);
- serializer.endTag(null, INCLUDE_NAME);
-
- if (mPort != null) {
- serializer.startTag(null, Configuration.RESULT_REPORTER_TYPE_NAME);
- serializer.attribute(null, ConfigurationUtil.CLASS_NAME, REPORTER_CLASS);
-
- serializer.startTag(null, ConfigurationUtil.OPTION_NAME);
- serializer.attribute(null, ConfigurationUtil.NAME_NAME, OPTION_KEY);
- serializer.attribute(null, ConfigurationUtil.VALUE_NAME, mPort);
- serializer.endTag(null, ConfigurationUtil.OPTION_NAME);
-
- serializer.endTag(null, Configuration.RESULT_REPORTER_TYPE_NAME);
+ final List<URL> urls = new ArrayList<>();
+ for (final String path : mClasspath.split(File.pathSeparator)) {
+ if (path.endsWith("*")) {
+ final File dir = new File(path.substring(0, path.length() - 1));
+ if (!dir.exists()) {
+ continue;
+ }
+ for (final File file :
+ dir.listFiles((parent, name) -> name.toLowerCase().endsWith(".jar"))) {
+ urls.add(file.toURI().toURL());
+ }
+ } else {
+ urls.add(new File(path).toURI().toURL());
+ }
}
- serializer.endTag(null, ConfigurationUtil.CONFIGURATION_NAME);
- serializer.endDocument();
+ // Read the original config file.
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ Document doc = null;
+ try (URLClassLoader loader = new URLClassLoader(urls.toArray(new URL[urls.size()]), null)) {
+ final DocumentBuilder builder = factory.newDocumentBuilder();
+ final String ext = FileUtil.getExtension(mOriginalConfig);
+ InputStream in = null;
+ if (Strings.isNullOrEmpty(ext)) {
+ in = loader.getResourceAsStream(String.format("config/%s.xml", mOriginalConfig));
+ } else {
+ in = loader.getResourceAsStream(String.format("config/%s", mOriginalConfig));
+ }
+ if (in == null) {
+ File f = new File(mOriginalConfig);
+ if (!f.isAbsolute()) {
+ f = new File(mWorkDir, mOriginalConfig);
+ }
+ try {
+ in = new FileInputStream(f);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(
+ String.format("Could not find configuration '%s'", mOriginalConfig));
+ }
+ }
+ doc = builder.parse(in);
+ } catch (ParserConfigurationException | SAXException e) {
+ throw new RuntimeException(e);
+ }
- writer.close();
+ if (mPort != null) {
+ // Add subprocess result reporter to a config file.
+ final Node root = doc.getElementsByTagName("configuration").item(0);
+ final Element reporter = doc.createElement(Configuration.RESULT_REPORTER_TYPE_NAME);
+ reporter.setAttribute(ConfigurationUtil.CLASS_NAME, REPORTER_CLASS);
+ final Element options = doc.createElement(ConfigurationUtil.OPTION_NAME);
+ options.setAttribute(ConfigurationUtil.NAME_NAME, OPTION_KEY);
+ options.setAttribute(ConfigurationUtil.VALUE_NAME, mPort);
+ reporter.appendChild(options);
+ root.appendChild(reporter);
+ }
+
+ File f = new File(mWorkDir, mOriginalConfig);
+ if (!f.exists() || !f.isFile()) {
+ // If the original config is an existing file, we need to update it since some old TFs
+ // check the file system first before bundled configs when loading configs.
+ // If the original config is not an existing file, we can use any name since the
+ // original config name will be assigned when creating a injection jar.
+ f = File.createTempFile("subprocess_config_", ".xml", mWorkDir);
+ }
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ try {
+ Transformer transformer = transformerFactory.newTransformer();
+ transformer.transform(new DOMSource(doc), new StreamResult(f));
+ } catch (TransformerException e) {
+ throw new RuntimeException(e);
+ }
+
return f;
}
}
diff --git a/src/com/android/tradefed/cluster/SubprocessReportingHelper.java b/src/com/android/tradefed/cluster/SubprocessReportingHelper.java
index d48522e..4944859 100644
--- a/src/com/android/tradefed/cluster/SubprocessReportingHelper.java
+++ b/src/com/android/tradefed/cluster/SubprocessReportingHelper.java
@@ -22,6 +22,8 @@
import com.android.tradefed.util.StreamUtil;
import com.android.tradefed.util.ZipUtil2;
+import com.google.common.base.Strings;
+
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -40,44 +42,71 @@
private static final String REPORTER_JAR_NAME = "subprocess-results-reporter.jar";
private static final String CLASS_FILTER =
String.format(
- "(^%s|^%s|^%s|^%s|^%s).*class$",
+ "(^%s|^%s|^%s|^%s|^%s|^%s).*class$",
+ "ErrorIdentifier",
"LegacySubprocessResultsReporter",
"SubprocessTestResultsParser",
"SubprocessEventHelper",
"SubprocessResultsReporter",
"ISupportGranularResults");
+ private String mCommandLine;
+ private String mClasspath;
+ private File mWorkDir;
+ private String mPort;
+
+ public SubprocessReportingHelper(
+ String commandLine, String classpath, File workDir, String port) {
+ mCommandLine = commandLine;
+ mClasspath = classpath;
+ mWorkDir = workDir;
+ mPort = port;
+ }
+
/**
* Dynamically generate extract .class file from tradefed.jar and generate new subprocess
* results reporter jar.
*
- * @param parentDir parent directory of subprocess results reporter jar.
- * @return subprocess result reporter jar.
+ * @return a subprocess result reporter jar to inject.
* @throws IOException
*/
- public File createSubprocessReporterJar(File parentDir) throws IOException {
- File reporterJar = new File(parentDir, REPORTER_JAR_NAME);
- File tfJar =
+ public File buildSubprocessReporterJar() throws IOException {
+ // Generate a patched config file.
+ final String[] tokens = QuotationAwareTokenizer.tokenizeLine(mCommandLine);
+ final String configName = tokens[0];
+ final SubprocessConfigBuilder builder = new SubprocessConfigBuilder();
+ builder.setWorkingDir(mWorkDir)
+ .setOriginalConfig(configName)
+ .setClasspath(mClasspath)
+ .setPort(mPort);
+ final File patchedConfigFile = builder.build();
+ LogUtil.CLog.i(
+ "Generating new configuration:\n %s",
+ FileUtil.readStringFromFile(patchedConfigFile));
+
+ final File reporterJar = new File(mWorkDir, REPORTER_JAR_NAME);
+ final File tfJar =
new File(
LegacySubprocessResultsReporter.class
.getProtectionDomain()
.getCodeSource()
.getLocation()
.getPath());
+ final String ext = FileUtil.getExtension(configName);
+ final String configFileName = Strings.isNullOrEmpty(ext) ? configName + ".xml" : configName;
// tfJar is directory of .class file when running JUnit test from Eclipse IDE
if (tfJar.isDirectory()) {
Set<File> classFiles = FileUtil.findFilesObject(tfJar, CLASS_FILTER);
Manifest manifest = new Manifest();
- createJar(reporterJar, manifest, classFiles);
- }
- // tfJar is the tradefed.jar when running with tradefed.
- else {
+ createJar(reporterJar, manifest, classFiles, configFileName, patchedConfigFile);
+ } else {
+ // tfJar is the tradefed.jar when running with tradefed.
File extractedJar = ZipUtil2.extractZipToTemp(tfJar, "tmp-jar");
try {
Set<File> classFiles = FileUtil.findFilesObject(extractedJar, CLASS_FILTER);
File mf = FileUtil.findFile(extractedJar, "MANIFEST.MF");
Manifest manifest = new Manifest(new FileInputStream(mf));
- createJar(reporterJar, manifest, classFiles);
+ createJar(reporterJar, manifest, classFiles, configFileName, patchedConfigFile);
} finally {
FileUtil.recursiveDelete(extractedJar);
}
@@ -92,7 +121,9 @@
* @param manifest manifest file.
* @throws IOException
*/
- private void createJar(File jar, Manifest manifest, Set<File> classFiles) throws IOException {
+ private void createJar(
+ File jar, Manifest manifest, Set<File> classFiles, String configName, File configFile)
+ throws IOException {
try (JarOutputStream jarOutput = new JarOutputStream(new FileOutputStream(jar), manifest)) {
for (File file : classFiles) {
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) {
@@ -104,30 +135,14 @@
jarOutput.closeEntry();
}
}
+ try (BufferedInputStream in =
+ new BufferedInputStream(new FileInputStream(configFile))) {
+ JarEntry entry = new JarEntry(String.format("config/%s", configName));
+ entry.setTime(configFile.lastModified());
+ jarOutput.putNextEntry(entry);
+ StreamUtil.copyStreams(in, jarOutput);
+ jarOutput.closeEntry();
+ }
}
}
-
- /**
- * Get a new command line whose configuration argument is replaced by a newly-created wrapper
- * configuration.
- *
- * <p>The resulting command line will reference a generate XML file in parentDir and needs to
- * run from parentDir.
- *
- * @param commandLine old command line that will be run by subprocess.
- * @param port port number that subprocess should use to report results.
- * @param parentDir parent directory of new wrapper configuration.
- * @return new command line, whose first argument is wrapper config.
- * @throws IOException
- */
- public String buildNewCommandConfig(String commandLine, String port, File parentDir)
- throws IOException {
- String[] tokens = QuotationAwareTokenizer.tokenizeLine(commandLine);
- SubprocessConfigBuilder builder = new SubprocessConfigBuilder();
- builder.setWorkingDir(parentDir).setOriginalConfig(tokens[0]).setPort(port);
- File f = builder.build();
- LogUtil.CLog.i("Generating new configuration:\n %s", FileUtil.readStringFromFile(f));
- tokens[0] = f.getName();
- return QuotationAwareTokenizer.combineTokens(tokens);
- }
}
diff --git a/src/com/android/tradefed/command/CommandOptions.java b/src/com/android/tradefed/command/CommandOptions.java
index 1760e9c..b3c5ed3 100644
--- a/src/com/android/tradefed/command/CommandOptions.java
+++ b/src/com/android/tradefed/command/CommandOptions.java
@@ -23,6 +23,7 @@
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.UniqueMultiMap;
+import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
@@ -108,6 +109,10 @@
"[0, shard-count)")
private Integer mShardIndex;
+ @Option(name = "optimize-mainline-test", description =
+ "Whether or not to optimize the list of test modules for mainline.")
+ private boolean mOptimizeMainlineTest;
+
@Option(
name = "enable-token-sharding",
description = "Whether or not to allow sharding with the token support enabled."
@@ -161,6 +166,12 @@
"For remote sharded invocation, whether or not to attempt the setup in parallel.")
private boolean mUseParallelRemoteSetup = false;
+ @Option(name = "parallel-setup", description = "Whether to attempt the setup in parallel.")
+ private boolean mUseParallelSetup = false;
+
+ @Option(name = "parallel-setup-timeout", description = "Timeout to use during parallel setup.")
+ private Duration mParallelSetupTimeout = Duration.ofMinutes(30L);
+
@Option(
name = "replicate-parent-setup",
description =
@@ -370,6 +381,14 @@
* {@inheritDoc}
*/
@Override
+ public boolean getOptimizeMainlineTest() {
+ return mOptimizeMainlineTest;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public Integer getShardCount() {
return mShardCount;
}
@@ -514,6 +533,18 @@
/** {@inheritDoc} */
@Override
+ public boolean shouldUseParallelSetup() {
+ return mUseParallelSetup;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Duration getParallelSetupTimeout() {
+ return mParallelSetupTimeout;
+ }
+
+ /** {@inheritDoc} */
+ @Override
public boolean shouldUseReplicateSetup() {
return mReplicateParentSetup;
}
diff --git a/src/com/android/tradefed/command/CommandRunner.java b/src/com/android/tradefed/command/CommandRunner.java
index fe77903..0cb94fe 100644
--- a/src/com/android/tradefed/command/CommandRunner.java
+++ b/src/com/android/tradefed/command/CommandRunner.java
@@ -21,6 +21,7 @@
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.device.NoDeviceException;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.SerializationUtil;
@@ -119,7 +120,10 @@
// After 1 min we check if the command was executed.
if (mScheduler.getReadyCommandCount() > 0
&& mScheduler.getExecutingCommandCount() == 0) {
- printStackTrace(new NoDeviceException("No device was allocated for the command."));
+ printStackTrace(
+ new NoDeviceException(
+ "No device was allocated for the command.",
+ InfraErrorIdentifier.RUNNER_ALLOCATION_ERROR));
mErrorCode = ExitCode.NO_DEVICE_ALLOCATED;
mScheduler.removeAllCommands();
mScheduler.shutdown();
diff --git a/src/com/android/tradefed/command/CommandScheduler.java b/src/com/android/tradefed/command/CommandScheduler.java
index 708b0d2..0ee2ca0 100644
--- a/src/com/android/tradefed/command/CommandScheduler.java
+++ b/src/com/android/tradefed/command/CommandScheduler.java
@@ -19,6 +19,7 @@
import com.android.ddmlib.DdmPreferences;
import com.android.ddmlib.Log;
import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.build.BuildRetrievalError;
import com.android.tradefed.clearcut.ClearcutClient;
import com.android.tradefed.command.CommandFileParser.CommandLine;
import com.android.tradefed.command.CommandFileWatcher.ICommandFileListener;
@@ -33,6 +34,7 @@
import com.android.tradefed.config.ConfigurationDescriptor;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.ConfigurationFactory;
+import com.android.tradefed.config.DynamicRemoteFileResolver;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.IConfigurationFactory;
@@ -67,6 +69,7 @@
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.ResultForwarder;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.result.suite.SuiteResultReporter;
import com.android.tradefed.sandbox.ISandbox;
import com.android.tradefed.testtype.IRemoteTest;
@@ -1073,8 +1076,9 @@
IConfiguration config = cmd.getConfiguration();
IInvocationContext context = new InvocationContext();
context.setConfigurationDescriptor(config.getConfigurationDescription());
- Map<String, ITestDevice> devices = allocateDevices(config, manager);
- if (!devices.isEmpty()) {
+ DeviceAllocationResult allocationResults = allocateDevices(config, manager);
+ if (allocationResults.wasAllocationSuccessful()) {
+ Map<String, ITestDevice> devices = allocationResults.getAllocatedDevices();
cmdIter.remove();
mExecutingCommands.add(cmd);
context.addAllocatedDevice(devices);
@@ -1183,12 +1187,27 @@
return foundSandbox;
}
- private boolean isProxyCommand(IConfiguration config) {
- return config.getConfigurationObject(ProxyConfiguration.PROXY_CONFIG_TYPE_KEY) != null;
+ private boolean isProxyCommand(String[] args) throws ConfigurationException {
+ ProxyConfiguration proxy = new ProxyConfiguration();
+ ArgsOptionParser argsParser = new ArgsOptionParser(proxy);
+ List<String> argsList = new ArrayList<>(Arrays.asList(args));
+ argsList.remove(0);
+ argsParser.parseBestEffort(argsList, true);
+ return proxy.isProxySet();
}
- private String[] handleProxyCommand(IConfiguration config, String[] originalArgs)
- throws ConfigurationException {
+ private IConfiguration handleProxyCommand(String[] originalArgs) throws ConfigurationException {
+ IConfiguration config =
+ ((ConfigurationFactory) getConfigFactory())
+ .createPartialConfigurationFromArgs(
+ originalArgs,
+ getKeyStoreClient(),
+ ImmutableSet.of(ProxyConfiguration.PROXY_CONFIG_TYPE_KEY));
+ try {
+ config.resolveDynamicOptions(new DynamicRemoteFileResolver());
+ } catch (BuildRetrievalError e) {
+ throw new ConfigurationException(e.getMessage(), e);
+ }
ProxyConfiguration proxy =
(ProxyConfiguration)
config.getConfigurationObject(ProxyConfiguration.PROXY_CONFIG_TYPE_KEY);
@@ -1196,7 +1215,7 @@
throw new ConfigurationException("No proxy configuration found.");
}
originalArgs[0] = proxy.getProxyConfig().getAbsolutePath();
- return originalArgs;
+ return config;
}
/** Returns true if the configuration used is a retry one. */
@@ -1217,31 +1236,28 @@
return GlobalConfiguration.getInstance().getSandboxFactory().createSandbox();
}
- private IConfiguration createConfiguration(String[] args) throws ConfigurationException {
- TradefedDelegator delegator = new TradefedDelegator();
- ArgsOptionParser argsParser = new ArgsOptionParser(delegator);
- List<String> argsList = new ArrayList<>(Arrays.asList(args));
- argsList.remove(0);
- argsParser.parseBestEffort(argsList, true);
+ protected IConfiguration createConfiguration(String[] args) throws ConfigurationException {
+ TradefedDelegator delegator = checkDelegation(args);
if (delegator.shouldUseDelegation()) {
- String[] argsWithoutDelegation = TradefedDelegator.clearCommandline(args);
- delegator.setCommandLine(argsWithoutDelegation);
- CLog.d(
- "Using commandline arguments as starting command: %s",
- Arrays.asList(argsWithoutDelegation));
- IConfiguration config =
- ((ConfigurationFactory) getConfigFactory())
- .createPartialConfigurationFromArgs(
- argsWithoutDelegation,
- getKeyStoreClient(),
- ImmutableSet.of(
- Configuration.DEVICE_REQUIREMENTS_TYPE_NAME,
- Configuration.LOGGER_TYPE_NAME,
- Configuration.LOG_SAVER_TYPE_NAME,
- Configuration.RESULT_REPORTER_TYPE_NAME));
- config.setConfigurationObject(TradefedDelegator.DELEGATE_OBJECT, delegator);
- setDelegateLevelReporting(config);
- return config;
+ args = TradefedDelegator.clearCommandline(args);
+ // Do not use delegation on staging
+ if (!delegator.isStaging()) {
+ delegator.setCommandLine(args);
+ CLog.d("Using commandline arguments as starting command: %s", Arrays.asList(args));
+ IConfiguration config =
+ ((ConfigurationFactory) getConfigFactory())
+ .createPartialConfigurationFromArgs(
+ args,
+ getKeyStoreClient(),
+ ImmutableSet.of(
+ Configuration.DEVICE_REQUIREMENTS_TYPE_NAME,
+ Configuration.LOGGER_TYPE_NAME,
+ Configuration.LOG_SAVER_TYPE_NAME,
+ Configuration.RESULT_REPORTER_TYPE_NAME));
+ config.setConfigurationObject(TradefedDelegator.DELEGATE_OBJECT, delegator);
+ setDelegateLevelReporting(config);
+ return config;
+ }
}
// check if the command should be sandboxed
@@ -1251,22 +1267,44 @@
return SandboxConfigurationFactory.getInstance()
.createConfigurationFromArgs(args, getKeyStoreClient(), sandbox, new RunUtil());
}
+ if (isProxyCommand(args)) {
+ IConfiguration proxyConfig = handleProxyCommand(args);
+ String[] argsWithoutDelegation = ProxyConfiguration.clearCommandline(args);
+ IConfiguration resolvedConfig = null;
+ try {
+ resolvedConfig =
+ getConfigFactory()
+ .createConfigurationFromArgs(
+ argsWithoutDelegation, null, getKeyStoreClient());
+ } catch (ConfigurationException e) {
+ proxyConfig.cleanConfigurationData();
+ throw e;
+ }
+ resolvedConfig.addFilesToClean(proxyConfig.getFilesToClean());
+ return resolvedConfig;
+ }
IConfiguration config =
getConfigFactory().createConfigurationFromArgs(args, null, getKeyStoreClient());
- if (isProxyCommand(config)) {
- String[] newArgs = handleProxyCommand(config, args);
- IConfiguration proxyConfig =
- getConfigFactory()
- .createConfigurationFromArgs(newArgs, null, getKeyStoreClient());
- proxyConfig.addFilesToClean(config.getFilesToClean());
- return proxyConfig;
- }
if (isRetryCommand(config)) {
return RetryConfigurationFactory.getInstance().createRetryConfiguration(config);
}
return config;
}
+ /**
+ * Create a delegator based on the command line to see if we need to delegate the run.
+ *
+ * @throws ConfigurationException
+ */
+ public static TradefedDelegator checkDelegation(String[] args) throws ConfigurationException {
+ TradefedDelegator delegator = new TradefedDelegator();
+ ArgsOptionParser argsParser = new ArgsOptionParser(delegator);
+ List<String> argsList = new ArrayList<>(Arrays.asList(args));
+ argsList.remove(0);
+ argsParser.parseBestEffort(argsList, true);
+ return delegator;
+ }
+
private void setDelegateLevelReporting(IConfiguration config) {
List<ITestInvocationListener> delegateReporters = new ArrayList<>();
// For debugging in the console, add a printer
@@ -1498,8 +1536,9 @@
ExecutableCommand execCmd = createExecutableCommand(cmdTracker, config, false);
context.setConfigurationDescriptor(config.getConfigurationDescription());
- Map<String, ITestDevice> devices = allocateDevices(config, manager);
- if (!devices.isEmpty()) {
+ DeviceAllocationResult allocationResults = allocateDevices(config, manager);
+ if (allocationResults.wasAllocationSuccessful()) {
+ Map<String, ITestDevice> devices = allocationResults.getAllocatedDevices();
context.addAllocatedDevice(devices);
synchronized (this) {
mExecutingCommands.add(execCmd);
@@ -1507,8 +1546,16 @@
CLog.d("Executing '%s' on '%s'", cmdTracker.getArgs()[0], devices);
startInvocation(context, execCmd, listener, new FreeDeviceHandler(manager));
} else {
+ // Log adb output just to help debug
+ String adbOutput =
+ ((DeviceManager) GlobalConfiguration.getDeviceManagerInstance())
+ .executeGlobalAdbCommand("devices");
+ CLog.e("'adb devices' output:\n%s", adbOutput);
throw new NoDeviceException(
- "no devices is available for command: " + Arrays.asList(args));
+ String.format(
+ "no devices is available for command: %s\n%s",
+ Arrays.asList(args), allocationResults.formattedReason()),
+ InfraErrorIdentifier.SCHEDULER_ALLOCATION_ERROR);
}
}
@@ -1549,11 +1596,12 @@
/**
* Allocate devices for a config.
+ *
* @param config a {@link IConfiguration} has device requirements.
* @param manager a {@link IDeviceManager}
* @return allocated devices
*/
- Map<String, ITestDevice> allocateDevices(IConfiguration config, IDeviceManager manager) {
+ DeviceAllocationResult allocateDevices(IConfiguration config, IDeviceManager manager) {
Map<String, ITestDevice> devices = new LinkedHashMap<String, ITestDevice>();
ITestDevice device = null;
if (config.getDeviceConfig().isEmpty()) {
@@ -1562,6 +1610,7 @@
// If we need to replicate the setup on all devices
ParentShardReplicate.replicatedSetup(config, getKeyStoreClient());
synchronized(this) {
+ DeviceAllocationResult allocationResults = new DeviceAllocationResult();
for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) {
device =
manager.allocateDevice(
@@ -1569,6 +1618,9 @@
if (device != null) {
devices.put(deviceConfig.getDeviceName(), device);
} else {
+ allocationResults.addAllocationFailureReason(
+ deviceConfig.getDeviceName(),
+ deviceConfig.getDeviceRequirements().getNoMatchReason());
// If one of the several device cannot be allocated, we de-allocate
// all the previous one.
for (ITestDevice allocatedDevice : devices.values()) {
@@ -1587,7 +1639,8 @@
break;
}
}
- return devices;
+ allocationResults.addAllocatedDevices(devices);
+ return allocationResults;
}
}
diff --git a/src/com/android/tradefed/command/DeviceAllocationResult.java b/src/com/android/tradefed/command/DeviceAllocationResult.java
new file mode 100644
index 0000000..849b04a
--- /dev/null
+++ b/src/com/android/tradefed/command/DeviceAllocationResult.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 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.command;
+
+import com.android.tradefed.device.ITestDevice;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/** Represents the results of an allocation attempt for a command. */
+public class DeviceAllocationResult {
+
+ private Map<String, String> mNotAllocatedReason = new LinkedHashMap<>();
+ private Map<String, ITestDevice> mAllocatedDevices = new LinkedHashMap<>();
+
+ /** returns whether or not the allocation was successful. */
+ public boolean wasAllocationSuccessful() {
+ return !mAllocatedDevices.isEmpty();
+ }
+
+ /** Add devices that have been allocated. */
+ public void addAllocatedDevices(Map<String, ITestDevice> devices) {
+ mAllocatedDevices.putAll(devices);
+ }
+
+ /** Add the reasons for not being allocated for each device config. */
+ public void addAllocationFailureReason(String deviceConfigName, Map<String, String> reasons) {
+ mNotAllocatedReason.put(deviceConfigName, createReasonMessage(reasons));
+ }
+
+ /** Returns the map of allocated devices */
+ public Map<String, ITestDevice> getAllocatedDevices() {
+ return mAllocatedDevices;
+ }
+
+ public String formattedReason() {
+ if (mNotAllocatedReason.size() == 1) {
+ return mNotAllocatedReason.values().iterator().next().toString();
+ }
+ return mNotAllocatedReason.toString();
+ }
+
+ private String createReasonMessage(Map<String, String> reasons) {
+ StringBuilder sb = new StringBuilder();
+ for (String serial : reasons.keySet()) {
+ String reason = reasons.get(serial);
+ if (reason == null) {
+ reason = "No reason provided";
+ }
+ sb.append(String.format("device '%s': %s", serial, reason));
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/com/android/tradefed/command/ICommandOptions.java b/src/com/android/tradefed/command/ICommandOptions.java
index 37dd022..ea2ce99 100644
--- a/src/com/android/tradefed/command/ICommandOptions.java
+++ b/src/com/android/tradefed/command/ICommandOptions.java
@@ -19,6 +19,7 @@
import com.android.tradefed.device.metric.AutoLogCollector;
import com.android.tradefed.util.UniqueMultiMap;
+import java.time.Duration;
import java.util.Map;
import java.util.Set;
@@ -120,6 +121,10 @@
*/
public void setInvocationTimeout(Long mInvocationTimeout);
+
+ /** Returns true if we should optimize the list of test modules for mainline test. */
+ public boolean getOptimizeMainlineTest();
+
/**
* Return the total shard count for the command.
*/
@@ -185,6 +190,12 @@
/** Whether or not to attempt parallel setup of the remote devices. */
public boolean shouldUseParallelRemoteSetup();
+ /** Whether or not to attempt parallel setup. */
+ public boolean shouldUseParallelSetup();
+
+ /** Returns the timeout to use during parallel setups. */
+ public Duration getParallelSetupTimeout();
+
/** Whether or not to use replicated setup for all the remote devices. */
public boolean shouldUseReplicateSetup();
diff --git a/src/com/android/tradefed/config/Configuration.java b/src/com/android/tradefed/config/Configuration.java
index e84fa3f..51d71c8 100644
--- a/src/com/android/tradefed/config/Configuration.java
+++ b/src/com/android/tradefed/config/Configuration.java
@@ -67,6 +67,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
@@ -104,7 +105,8 @@
// regexp pattern used to parse map option values
private static final Pattern OPTION_KEY_VALUE_PATTERN = Pattern.compile("(?<!\\\\)=");
- private static final String CONFIG_EXCEPTION_PATTERN = "Could not find option with name ";
+ private static final Pattern CONFIG_EXCEPTION_PATTERN =
+ Pattern.compile("Could not find option with name '(.*)'");
/** Mapping of config object type name to config objects. */
private Map<String, List<Object>> mConfigMap;
@@ -1123,10 +1125,11 @@
try {
return parser.parse(listArgs);
} catch (ConfigurationException e) {
- if (!e.getMessage().contains(CONFIG_EXCEPTION_PATTERN)) {
+ Matcher m = CONFIG_EXCEPTION_PATTERN.matcher(e.getMessage());
+ if (!m.matches()) {
throw e;
}
- String optionName = e.getMessage().split(CONFIG_EXCEPTION_PATTERN)[1];
+ String optionName = m.group(1);
try {
// In case the option exists in the config descriptor, we change the error message
// to be more specific about why the option is rejected.
@@ -1138,7 +1141,7 @@
}
throw new OptionNotAllowedException(
String.format(
- "Option %s cannot be specified via "
+ "Option '%s' cannot be specified via "
+ "command line. Only in the configuration xml.",
optionName));
}
diff --git a/src/com/android/tradefed/config/DynamicRemoteFileResolver.java b/src/com/android/tradefed/config/DynamicRemoteFileResolver.java
index 9bfc06c..ae2ee62 100644
--- a/src/com/android/tradefed/config/DynamicRemoteFileResolver.java
+++ b/src/com/android/tradefed/config/DynamicRemoteFileResolver.java
@@ -380,7 +380,7 @@
fileToResolve = new File(protocol + ":" + uri.getPath());
} catch (URISyntaxException e) {
CLog.e(e);
- return null;
+ throw new BuildRetrievalError(e.getMessage(), e);
}
try {
diff --git a/src/com/android/tradefed/config/OptionSetter.java b/src/com/android/tradefed/config/OptionSetter.java
index 49d0bec..6b08ec32 100644
--- a/src/com/android/tradefed/config/OptionSetter.java
+++ b/src/com/android/tradefed/config/OptionSetter.java
@@ -330,8 +330,8 @@
private OptionFieldsForName fieldsForArg(String name) throws ConfigurationException {
OptionFieldsForName fields = fieldsForArgNoThrow(name);
if (fields == null) {
- throw new ConfigurationException(String.format("Could not find option with name %s",
- name));
+ throw new ConfigurationException(
+ String.format("Could not find option with name '%s'", name));
}
return fields;
}
diff --git a/src/com/android/tradefed/config/proxy/ProxyConfiguration.java b/src/com/android/tradefed/config/proxy/ProxyConfiguration.java
index bfe631d..86b7e50 100644
--- a/src/com/android/tradefed/config/proxy/ProxyConfiguration.java
+++ b/src/com/android/tradefed/config/proxy/ProxyConfiguration.java
@@ -15,31 +15,58 @@
*/
package com.android.tradefed.config.proxy;
+import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.Option;
import com.android.tradefed.log.LogUtil.CLog;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
/** Object that allows pointing to a remote configuration to execute. */
public final class ProxyConfiguration {
public static final String PROXY_CONFIG_TYPE_KEY = "proxy-config";
+ private static final String PROXY_CONFIG_OPTION_NAME = "proxy-configuration";
@Option(
- name = "proxy-configuration",
+ name = PROXY_CONFIG_OPTION_NAME,
description = "Point to an external configuration to be run instead.")
private File mProxyConfig;
+ /** Returns whether or not a proxy config value is set. */
+ public boolean isProxySet() {
+ return mProxyConfig != null;
+ }
+
/** Returns the current proxy configuration to use. */
public File getProxyConfig() {
if (mProxyConfig == null || !mProxyConfig.exists()) {
- CLog.d("No proxy configuration is configured.");
+ CLog.e("No proxy configuration is configured: %s", mProxyConfig);
return null;
}
if (mProxyConfig.isDirectory()) {
- CLog.w("Proxy configuration must be a file, found a directory: %s", mProxyConfig);
+ CLog.e("Proxy configuration must be a file, found a directory: %s", mProxyConfig);
return null;
}
return mProxyConfig;
}
+
+ public static String[] clearCommandline(String[] originalCommand)
+ throws ConfigurationException {
+ List<String> argsList = new ArrayList<>(Arrays.asList(originalCommand));
+ try {
+ while (argsList.contains("--" + PROXY_CONFIG_OPTION_NAME)) {
+ int index = argsList.indexOf("--" + PROXY_CONFIG_OPTION_NAME);
+ if (index != -1) {
+ argsList.remove(index + 1);
+ argsList.remove(index);
+ }
+ }
+ } catch (RuntimeException e) {
+ throw new ConfigurationException(e.getMessage(), e);
+ }
+ return argsList.toArray(new String[0]);
+ }
}
diff --git a/src/com/android/tradefed/config/proxy/TradefedDelegator.java b/src/com/android/tradefed/config/proxy/TradefedDelegator.java
index d50c8a1..17af019 100644
--- a/src/com/android/tradefed/config/proxy/TradefedDelegator.java
+++ b/src/com/android/tradefed/config/proxy/TradefedDelegator.java
@@ -15,16 +15,20 @@
*/
package com.android.tradefed.config.proxy;
+import com.android.tradefed.command.CommandOptions;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.Option;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.UniqueMultiMap;
import com.google.common.base.Joiner;
import java.io.File;
-import java.io.FileFilter;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
/** Objects that helps delegating the invocation to another Tradefed binary. */
public class TradefedDelegator {
@@ -40,6 +44,11 @@
"Points to the root dir of another Tradefed binary that will be used to drive the invocation")
private File mDelegatedTfRootDir;
+ @Option(
+ name = CommandOptions.INVOCATION_DATA,
+ description = "Mirror of CommandOptions#INVOCATION_DATA")
+ private UniqueMultiMap<String, String> mInvocationData = new UniqueMultiMap<>();
+
private String[] mCommandLine = null;
/** Whether or not trigger the delegation logic. */
@@ -53,16 +62,8 @@
}
/** Creates the classpath out of the jars in the directory. */
- public String createClasspath() {
- List<File> jars =
- Arrays.asList(
- mDelegatedTfRootDir.listFiles(
- new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- return pathname.getName().endsWith(".jar");
- }
- }));
+ public String createClasspath() throws IOException {
+ Set<File> jars = FileUtil.findFilesObject(mDelegatedTfRootDir, ".*\\.jar");
return Joiner.on(":").join(jars);
}
@@ -74,12 +75,32 @@
return mCommandLine;
}
+ /**
+ * Returns whether or not this is the staging environment. We do not want to delegate in staging
+ * by default, only if the "staging_delegated" is set.
+ */
+ public boolean isStaging() {
+ return mInvocationData.containsKey("staging")
+ && !mInvocationData.containsKey("staging_delegated");
+ }
+
+ /**
+ * Remove from the original command line the delegate options so the underlying config does not
+ * delegate again.
+ */
public static String[] clearCommandline(String[] originalCommand)
throws ConfigurationException {
+ String[] commandLine = clearCommandlineFromOneArg(originalCommand, DELETEGATED_OPTION_NAME);
+ return commandLine;
+ }
+
+ /** Remove a given option from the command line. */
+ private static String[] clearCommandlineFromOneArg(String[] originalCommand, String optionName)
+ throws ConfigurationException {
List<String> argsList = new ArrayList<>(Arrays.asList(originalCommand));
try {
- while (argsList.contains("--" + DELETEGATED_OPTION_NAME)) {
- int index = argsList.indexOf("--" + DELETEGATED_OPTION_NAME);
+ while (argsList.contains("--" + optionName)) {
+ int index = argsList.indexOf("--" + optionName);
if (index != -1) {
argsList.remove(index + 1);
argsList.remove(index);
diff --git a/src/com/android/tradefed/config/remote/GcsRemoteFileResolver.java b/src/com/android/tradefed/config/remote/GcsRemoteFileResolver.java
index 64ba61f..68e94cd 100644
--- a/src/com/android/tradefed/config/remote/GcsRemoteFileResolver.java
+++ b/src/com/android/tradefed/config/remote/GcsRemoteFileResolver.java
@@ -20,6 +20,7 @@
import com.android.tradefed.build.gcs.GCSDownloaderHelper;
import com.android.tradefed.config.DynamicRemoteFileResolver;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import java.io.File;
import java.io.IOException;
@@ -44,10 +45,12 @@
File downloadedFile = getDownloader().fetchTestResource(path);
// Unzip it if required
return DynamicRemoteFileResolver.unzipIfRequired(downloadedFile, query);
- } catch (BuildRetrievalError | IOException e) {
+ } catch (IOException e) {
CLog.e(e);
throw new BuildRetrievalError(
- String.format("Failed to download %s due to: %s", path, e.getMessage()), e);
+ String.format("Failed to download %s due to: %s", path, e.getMessage()),
+ e,
+ InfraErrorIdentifier.GCS_ERROR);
}
}
diff --git a/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java b/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java
index 1aef60c..7a84771 100644
--- a/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java
+++ b/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java
@@ -21,6 +21,7 @@
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.config.yaml.IDefaultObjectLoader.LoaderConfiguration;
+import com.android.tradefed.config.yaml.YamlClassOptionsParser.ClassAndOptions;
import com.google.common.collect.ImmutableList;
@@ -40,6 +41,8 @@
public final class ConfigurationYamlParser {
private static final String DESCRIPTION_KEY = "description";
+ public static final String PRE_SETUP_ACTION_KEY = "pre_setup_action";
+ public static final String POST_SETUP_ACTION_KEY = "post_setup_action";
public static final String DEPENDENCIES_KEY = "dependencies";
public static final String TESTS_KEY = "tests";
@@ -89,6 +92,16 @@
mSeenKeys.add(DESCRIPTION_KEY);
}
Set<String> dependencyFiles = new LinkedHashSet<>();
+ if (yamlObjects.containsKey(PRE_SETUP_ACTION_KEY)) {
+ YamlClassOptionsParser classAndOptions =
+ new YamlClassOptionsParser(
+ "action",
+ PRE_SETUP_ACTION_KEY,
+ (List<Map<String, Object>>) yamlObjects.get(PRE_SETUP_ACTION_KEY));
+ mSeenKeys.add(PRE_SETUP_ACTION_KEY);
+ convertClassAndOptionsToObjects(
+ configDef, classAndOptions, Configuration.TARGET_PREPARER_TYPE_NAME);
+ }
if (yamlObjects.containsKey(DEPENDENCIES_KEY)) {
YamlTestDependencies testDeps =
new YamlTestDependencies(
@@ -97,12 +110,24 @@
mSeenKeys.add(DEPENDENCIES_KEY);
}
if (yamlObjects.containsKey(TESTS_KEY)) {
- YamlTestRunners runnerInfo =
- new YamlTestRunners((List<Map<String, Object>>) yamlObjects.get(TESTS_KEY));
+ YamlClassOptionsParser runnerInfo =
+ new YamlClassOptionsParser(
+ "test",
+ TESTS_KEY,
+ (List<Map<String, Object>>) yamlObjects.get(TESTS_KEY));
mSeenKeys.add(TESTS_KEY);
- convertTestsToObjects(configDef, runnerInfo);
+ convertClassAndOptionsToObjects(configDef, runnerInfo, Configuration.TEST_TYPE_NAME);
}
-
+ if (yamlObjects.containsKey(POST_SETUP_ACTION_KEY)) {
+ YamlClassOptionsParser runnerInfo =
+ new YamlClassOptionsParser(
+ "action",
+ POST_SETUP_ACTION_KEY,
+ (List<Map<String, Object>>) yamlObjects.get(POST_SETUP_ACTION_KEY));
+ mSeenKeys.add(POST_SETUP_ACTION_KEY);
+ convertClassAndOptionsToObjects(
+ configDef, runnerInfo, Configuration.TARGET_PREPARER_TYPE_NAME);
+ }
if (!mSeenKeys.containsAll(REQUIRED_KEYS)) {
Set<String> missingKeys = new HashSet<>(REQUIRED_KEYS);
missingKeys.removeAll(mSeenKeys);
@@ -185,27 +210,26 @@
return dependencies;
}
- private void convertTestsToObjects(ConfigurationDef def, YamlTestRunners tests) {
- if (tests.getRunner() == null) {
+ private void convertClassAndOptionsToObjects(
+ ConfigurationDef def, YamlClassOptionsParser tests, String configObjType) {
+ if (tests.getClassesAndOptions().isEmpty()) {
return;
}
- String className = tests.getRunner();
- int classCount = def.addConfigObjectDef(Configuration.TEST_TYPE_NAME, className);
- for (Entry<String, String> options : tests.getOptions().entries()) {
- String optionName =
- String.format(
- "%s%c%d%c%s",
- className,
- OptionSetter.NAMESPACE_SEPARATOR,
- classCount,
- OptionSetter.NAMESPACE_SEPARATOR,
- options.getKey());
- def.addOptionDef(
- optionName,
- null,
- options.getValue(),
- def.getName(),
- Configuration.TEST_TYPE_NAME);
+ for (ClassAndOptions classOptions : tests.getClassesAndOptions()) {
+ String className = classOptions.mClass;
+ int classCount = def.addConfigObjectDef(configObjType, className);
+ for (Entry<String, String> options : classOptions.mOptions.entries()) {
+ String optionName =
+ String.format(
+ "%s%c%d%c%s",
+ className,
+ OptionSetter.NAMESPACE_SEPARATOR,
+ classCount,
+ OptionSetter.NAMESPACE_SEPARATOR,
+ options.getKey());
+ def.addOptionDef(
+ optionName, null, options.getValue(), def.getName(), configObjType);
+ }
}
}
}
diff --git a/src/com/android/tradefed/config/yaml/YamlClassOptionsParser.java b/src/com/android/tradefed/config/yaml/YamlClassOptionsParser.java
new file mode 100644
index 0000000..3dcdae1
--- /dev/null
+++ b/src/com/android/tradefed/config/yaml/YamlClassOptionsParser.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 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.config.yaml;
+
+import com.android.tradefed.config.ConfigurationException;
+
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/** Helper to parse test runner information from the YAML Tradefed Configuration. */
+public class YamlClassOptionsParser {
+
+ private static final String CLASS_NAME_KEY = "name";
+ private static final String OPTIONS_KEY = "options";
+
+ class ClassAndOptions {
+ public String mClass;
+ public Multimap<String, String> mOptions = LinkedListMultimap.create();
+ }
+
+ private List<ClassAndOptions> mListClassAndOptions = new ArrayList<>();
+
+ public YamlClassOptionsParser(String mainkey, String category, List<Map<String, Object>> tests)
+ throws ConfigurationException {
+ for (Map<String, Object> runnerEntry : tests) {
+ if (runnerEntry.containsKey(mainkey)) {
+ ClassAndOptions classOptions = new ClassAndOptions();
+ mListClassAndOptions.add(classOptions);
+ for (Entry<String, Object> entry :
+ ((Map<String, Object>) runnerEntry.get(mainkey)).entrySet()) {
+ if (CLASS_NAME_KEY.equals(entry.getKey())) {
+ classOptions.mClass = (String) entry.getValue();
+ }
+ if (OPTIONS_KEY.equals(entry.getKey())) {
+ for (Map<String, Object> optionMap :
+ (List<Map<String, Object>>) entry.getValue()) {
+ for (Entry<String, Object> optionVal : optionMap.entrySet()) {
+ // TODO: Support map option
+ classOptions.mOptions.put(
+ optionVal.getKey(), optionVal.getValue().toString());
+ }
+ }
+ }
+ }
+ } else {
+ throw new ConfigurationException(
+ String.format("'%s' key is mandatory in '%s'", mainkey, category));
+ }
+ }
+ }
+
+ public List<ClassAndOptions> getClassesAndOptions() {
+ return mListClassAndOptions;
+ }
+}
diff --git a/src/com/android/tradefed/config/yaml/YamlTestRunners.java b/src/com/android/tradefed/config/yaml/YamlTestRunners.java
deleted file mode 100644
index 1c8952e..0000000
--- a/src/com/android/tradefed/config/yaml/YamlTestRunners.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2020 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.config.yaml;
-
-import com.android.tradefed.config.ConfigurationException;
-
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.Multimap;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/** Helper to parse test runner information from the YAML Tradefed Configuration. */
-public class YamlTestRunners {
-
- private static final String TEST_KEY = "test";
- private static final String TEST_NAME_KEY = "name";
- private static final String OPTIONS_KEY = "options";
-
- private String mRunner;
- private Multimap<String, String> mOptions = LinkedListMultimap.create();
-
- public YamlTestRunners(List<Map<String, Object>> tests) throws ConfigurationException {
- if (tests.size() > 1) {
- throw new ConfigurationException("Currently only support one runner at a time.");
- }
- for (Map<String, Object> runnerEntry : tests) {
- if (runnerEntry.containsKey(TEST_KEY)) {
- for (Entry<String, Object> entry :
- ((Map<String, Object>) runnerEntry.get(TEST_KEY)).entrySet()) {
- if (TEST_NAME_KEY.equals(entry.getKey())) {
- mRunner = (String) entry.getValue();
- }
- if (OPTIONS_KEY.equals(entry.getKey())) {
- for (Map<String, Object> optionMap :
- (List<Map<String, Object>>) entry.getValue()) {
- for (Entry<String, Object> optionVal : optionMap.entrySet()) {
- // TODO: Support map option
- mOptions.put(optionVal.getKey(), optionVal.getValue().toString());
- }
- }
- }
- }
- } else {
- throw new ConfigurationException(
- String.format(
- "'%s' key is mandatory in '%s'",
- TEST_KEY, ConfigurationYamlParser.TESTS_KEY));
- }
- }
- }
-
- /** Returns the test runner to be used. */
- public String getRunner() {
- return mRunner;
- }
-
- /** Returns the options for the test runner */
- public Multimap<String, String> getOptions() {
- return mOptions;
- }
-}
diff --git a/src/com/android/tradefed/device/DeviceManager.java b/src/com/android/tradefed/device/DeviceManager.java
index cf20f87..6694960 100644
--- a/src/com/android/tradefed/device/DeviceManager.java
+++ b/src/com/android/tradefed/device/DeviceManager.java
@@ -34,6 +34,7 @@
import com.android.tradefed.log.ILogRegistry.EventType;
import com.android.tradefed.log.LogRegistry;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
@@ -1016,8 +1017,10 @@
@Override
public void recoverDevice(IDeviceStateMonitor monitor, boolean recoverUntilOnline)
throws DeviceNotAvailableException {
- throw new DeviceNotAvailableException("aborted test session",
- monitor.getSerialNumber());
+ throw new DeviceNotAvailableException(
+ "aborted test session",
+ monitor.getSerialNumber(),
+ InfraErrorIdentifier.INVOCATION_CANCELLED);
}
/**
@@ -1026,8 +1029,10 @@
@Override
public void recoverDeviceBootloader(IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
- throw new DeviceNotAvailableException("aborted test session",
- monitor.getSerialNumber());
+ throw new DeviceNotAvailableException(
+ "aborted test session",
+ monitor.getSerialNumber(),
+ InfraErrorIdentifier.INVOCATION_CANCELLED);
}
/**
@@ -1036,8 +1041,10 @@
@Override
public void recoverDeviceRecovery(IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
- throw new DeviceNotAvailableException("aborted test session",
- monitor.getSerialNumber());
+ throw new DeviceNotAvailableException(
+ "aborted test session",
+ monitor.getSerialNumber(),
+ InfraErrorIdentifier.INVOCATION_CANCELLED);
}
/** {@inheritDoc} */
@@ -1045,7 +1052,9 @@
public void recoverDeviceFastbootd(IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
throw new DeviceNotAvailableException(
- "aborted test session", monitor.getSerialNumber());
+ "aborted test session",
+ monitor.getSerialNumber(),
+ InfraErrorIdentifier.INVOCATION_CANCELLED);
}
}
diff --git a/src/com/android/tradefed/device/DeviceSelectionOptions.java b/src/com/android/tradefed/device/DeviceSelectionOptions.java
index 6dbae6d..fac18c7 100644
--- a/src/com/android/tradefed/device/DeviceSelectionOptions.java
+++ b/src/com/android/tradefed/device/DeviceSelectionOptions.java
@@ -29,6 +29,7 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
@@ -161,6 +162,8 @@
// If we have tried to fetch the environment variable ANDROID_SERIAL before.
private boolean mFetchedEnvVariable = false;
+ // Store the reason for which the device was not matched.
+ private Map<String, String> mNoMatchReason = new LinkedHashMap<>();
private static final String VARIANT_SEPARATOR = ":";
@@ -448,6 +451,7 @@
*/
@Override
public boolean matches(IDevice device) {
+ String deviceSerial = device.getSerialNumber();
Collection<String> serials = getSerials(device);
Collection<String> excludeSerials = getExcludeSerials();
Map<String, Collection<String>> productVariants = splitOnVariant(getProductTypes());
@@ -456,9 +460,17 @@
if (!serials.isEmpty() &&
!serials.contains(device.getSerialNumber())) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device serial does not match any requested serial(%s)", serials));
return false;
}
if (excludeSerials.contains(device.getSerialNumber())) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device serial was part of excluded serials(%s)", excludeSerials));
return false;
}
if (!productTypes.isEmpty()) {
@@ -468,15 +480,31 @@
String productVariant = getDeviceProductVariant(device);
Collection<String> variants = productVariants.get(productType);
if (variants != null && !variants.contains(productVariant)) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device variant (%s) does not match requested variants(%s)",
+ productVariant, variants));
return false;
}
} else {
// no product type matches; bye-bye
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device product type (%s) does not match requested product types(%s)",
+ productType, productTypes));
return false;
}
}
for (Map.Entry<String, String> propEntry : properties.entrySet()) {
- if (!propEntry.getValue().equals(device.getProperty(propEntry.getKey()))) {
+ String deviceProperty = device.getProperty(propEntry.getKey());
+ if (!propEntry.getValue().equals(deviceProperty)) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device property (%s) value(%s) does not match requested value(%s)",
+ propEntry.getKey(), deviceProperty, propEntry.getValue()));
return false;
}
}
@@ -488,12 +516,25 @@
if ((mMinSdk != null) || (mMaxSdk != null)) {
int deviceSdkLevel = getDeviceSdkLevel(device);
if (deviceSdkLevel < 0) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format("device returned unexpected sdk level (%s)", deviceSdkLevel));
return false;
}
if (mMinSdk != null && deviceSdkLevel < mMinSdk) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device sdk (%s) is below the requested min sdk (%s)",
+ deviceSdkLevel, mMinSdk));
return false;
}
if (mMaxSdk != null && mMaxSdk < deviceSdkLevel) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device sdk (%s) is above the requested max sdk (%s)",
+ deviceSdkLevel, mMaxSdk));
return false;
}
}
@@ -505,19 +546,35 @@
if (device instanceof StubDevice || device instanceof FastbootDevice) {
// Reading battery of fastboot and StubDevice device does not work and could
// lead to weird log.
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device type is (%s) which cannot have a battery required.",
+ device.getClass()));
return false;
}
Integer deviceBattery = getBatteryLevel(device);
if (deviceBattery == null) {
// Couldn't determine battery level when that check is required; reject device
+ addNoMatchReason(deviceSerial, "device failed to return a battery reading.");
return false;
}
if (isLessAndNotNull(deviceBattery, mMinBattery)) {
// deviceBattery < mMinBattery
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device battery (%s) is below the requested min battery (%s)",
+ deviceBattery, mMinBattery));
return false;
}
if (isLessEqAndNotNull(mMaxBattery, deviceBattery)) {
// mMaxBattery <= deviceBattery
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device battery (%s) is above the requested max battery (%s)",
+ deviceBattery, mMaxBattery));
return false;
}
}
@@ -558,39 +615,55 @@
if ((emulatorRequested() || stubEmulatorRequested()) && !device.isEmulator()) {
return false;
}
+ String deviceSerial = device.getSerialNumber();
// If physical device is requested but device is emulator or remote ip device, skip
if (deviceRequested()
&& (device.isEmulator()
|| RemoteAndroidDevice.checkSerialFormatValid(device.getSerialNumber()))) {
+ addNoMatchReason(deviceSerial, "device is not a physical device");
return false;
}
if (mRequestedType != null) {
Class<?> classNeeded = mRequestedType.getRequiredClass();
if (!device.getClass().equals(classNeeded)) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device is type (%s) while requested type was (%s)",
+ device.getClass(), classNeeded));
return false;
}
} else {
if (device.isEmulator() && (device instanceof StubDevice) && !stubEmulatorRequested()) {
// only allocate the stub emulator if requested
+ addNoMatchReason(deviceSerial, "device is emulator while requested type was not");
return false;
}
if (nullDeviceRequested() != (device instanceof NullDevice)) {
+ addNoMatchReason(
+ deviceSerial, "device is null-device while requested type was not");
return false;
}
if (tcpDeviceRequested() != TcpDevice.class.equals(device.getClass())) {
// We only match an exact TcpDevice here, no child class.
+ addNoMatchReason(deviceSerial, "device is tcp-device while requested type was not");
return false;
}
if (gceDeviceRequested() != RemoteAvdIDevice.class.equals(device.getClass())) {
// We only match an exact RemoteAvdIDevice here, no child class.
+ addNoMatchReason(deviceSerial, "device is gce-device while requested type was not");
return false;
}
if (remoteDeviceRequested() != VmRemoteDevice.class.equals(device.getClass())) {
+ addNoMatchReason(
+ deviceSerial, "device is remote-device while requested type was not");
return false;
}
if (localVirtualDeviceRequested()
!= StubLocalAndroidVirtualDevice.class.equals(device.getClass())) {
+ addNoMatchReason(
+ deviceSerial, "device is local-virtual while requested type was not");
return false;
}
}
@@ -704,6 +777,15 @@
return apiLevel;
}
+ private void addNoMatchReason(String device, String reason) {
+ mNoMatchReason.put(device, reason);
+ }
+
+ @Override
+ public Map<String, String> getNoMatchReason() {
+ return mNoMatchReason;
+ }
+
/**
* Helper factory method to create a {@link IDeviceSelection} that will only match device
* with given serial
diff --git a/src/com/android/tradefed/device/IDeviceSelection.java b/src/com/android/tradefed/device/IDeviceSelection.java
index c61179e..1302532 100644
--- a/src/com/android/tradefed/device/IDeviceSelection.java
+++ b/src/com/android/tradefed/device/IDeviceSelection.java
@@ -116,4 +116,10 @@
*/
public void setSerial(String... serialNumber);
+ /**
+ * Returns the reason for which the device was not matched.
+ *
+ * @return a Map of serial number to reason for which it wasn't allocated
+ */
+ public Map<String, String> getNoMatchReason();
}
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index fcd8635..e859431 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -107,6 +107,7 @@
public class NativeDevice implements IManagedTestDevice {
protected static final String SD_CARD = "/sdcard/";
+ protected static final String STORAGE_EMULATED = "/storage/emulated/";
/**
* Allow pauses of up to 2 minutes while receiving bugreport.
* <p/>
@@ -1089,7 +1090,7 @@
public boolean pullFile(final String remoteFilePath, final File localFile)
throws DeviceNotAvailableException {
- if (remoteFilePath.startsWith(SD_CARD)) {
+ if (isSdcardOrEmulated(remoteFilePath)) {
ContentProviderHandler handler = getContentProvider();
if (handler != null) {
return handler.pullFile(remoteFilePath, localFile);
@@ -1197,7 +1198,7 @@
@Override
public boolean pushFile(final File localFile, final String remoteFilePath)
throws DeviceNotAvailableException {
- if (remoteFilePath.startsWith(SD_CARD)) {
+ if (isSdcardOrEmulated(remoteFilePath)) {
ContentProviderHandler handler = getContentProvider();
if (handler != null) {
return handler.pushFile(localFile, remoteFilePath);
@@ -1272,6 +1273,15 @@
/** {@inheritDoc} */
@Override
public boolean doesFileExist(String deviceFilePath) throws DeviceNotAvailableException {
+ if (isSdcardOrEmulated(deviceFilePath)) {
+ ContentProviderHandler handler = getContentProvider();
+ if (handler != null) {
+ CLog.d("Delegating check to ContentProvider doesFileExist(%s)", deviceFilePath);
+
+ return handler.doesFileExist(deviceFilePath);
+ }
+ }
+ CLog.d("Using 'ls' to check doesFileExist(%s)", deviceFilePath);
String lsGrep = executeShellCommand(String.format("ls \"%s\"", deviceFilePath));
return !lsGrep.contains("No such file or directory");
}
@@ -1279,7 +1289,7 @@
/** {@inheritDoc} */
@Override
public void deleteFile(String deviceFilePath) throws DeviceNotAvailableException {
- if (deviceFilePath.startsWith(SD_CARD)) {
+ if (isSdcardOrEmulated(deviceFilePath)) {
ContentProviderHandler handler = getContentProvider();
if (handler != null) {
if (handler.deleteFile(deviceFilePath)) {
@@ -1612,7 +1622,7 @@
@Override
public boolean pullDir(String deviceFilePath, File localDir)
throws DeviceNotAvailableException {
- if (deviceFilePath.startsWith(SD_CARD)) {
+ if (isSdcardOrEmulated(deviceFilePath)) {
ContentProviderHandler handler = getContentProvider();
if (handler != null) {
return handler.pullDir(deviceFilePath, localDir);
@@ -1665,6 +1675,11 @@
return true;
}
+ /** Checks whether path is external storage path. */
+ private boolean isSdcardOrEmulated(String path) {
+ return path.startsWith(SD_CARD) || path.startsWith(STORAGE_EMULATED);
+ }
+
/**
* {@inheritDoc}
*/
@@ -2099,9 +2114,13 @@
recoverDevice();
}
if (retryAttempts > 0) {
- throw new DeviceUnresponsiveException(String.format("Attempted %s multiple times "
- + "on device %s without communication success. Aborting.", actionDescription,
- getSerialNumber()), getSerialNumber());
+ throw new DeviceUnresponsiveException(
+ String.format(
+ "Attempted %s multiple times "
+ + "on device %s without communication success. Aborting.",
+ actionDescription, getSerialNumber()),
+ getSerialNumber(),
+ DeviceErrorIdentifier.DEVICE_UNRESPONSIVE);
}
return false;
}
@@ -3950,7 +3969,8 @@
throw new DeviceRuntimeException(
String.format(
"Failed to query property '%s'. device returned null.",
- DeviceProperties.BUILD_CODENAME));
+ DeviceProperties.BUILD_CODENAME),
+ DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
codeName = codeName.trim();
int apiLevel = getApiLevel() + ("REL".equals(codeName) ? 0 : 1);
diff --git a/src/com/android/tradefed/device/NoDeviceException.java b/src/com/android/tradefed/device/NoDeviceException.java
index 45f4339..a2fae93 100644
--- a/src/com/android/tradefed/device/NoDeviceException.java
+++ b/src/com/android/tradefed/device/NoDeviceException.java
@@ -16,36 +16,23 @@
package com.android.tradefed.device;
import com.android.tradefed.build.BuildSerializedVersion;
+import com.android.tradefed.error.HarnessRuntimeException;
+import com.android.tradefed.result.error.ErrorIdentifier;
-/**
- * Thrown when there's no device to execute a given command.
- */
-public class NoDeviceException extends Exception {
+import java.lang.StackWalker.Option;
+
+/** Thrown when there's no device to execute a given command. */
+public class NoDeviceException extends HarnessRuntimeException {
private static final long serialVersionUID = BuildSerializedVersion.VERSION;
/**
* Creates a {@link NoDeviceException}.
- */
- public NoDeviceException() {
- super();
- }
-
- /**
- * Creates a {@link NoDeviceException}.
*
* @param msg a descriptive message.
+ * @param errorId The {@link ErrorIdentifier} categorizing the exception.
*/
- public NoDeviceException(String msg) {
- super(msg);
- }
-
- /**
- * Creates a {@link NoDeviceException}.
- *
- * @param msg a descriptive message.
- * @param cause the root {@link Throwable} that caused the device to become unavailable.
- */
- public NoDeviceException(String msg, Throwable cause) {
- super(msg, cause);
+ public NoDeviceException(String msg, ErrorIdentifier errorId) {
+ super(msg, errorId);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
}
}
diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java
index 80fb98d..958f21d 100644
--- a/src/com/android/tradefed/device/TestDevice.java
+++ b/src/com/android/tradefed/device/TestDevice.java
@@ -27,6 +27,7 @@
import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.util.AaptParser;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
@@ -1187,7 +1188,8 @@
String[] lines = commandOutput.split("\\r?\\n");
if (!lines[0].equals("Users:")) {
throw new DeviceRuntimeException(
- String.format("'%s' in not a valid output for 'pm list users'", commandOutput));
+ String.format("'%s' in not a valid output for 'pm list users'", commandOutput),
+ DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
ArrayList<String[]> users = new ArrayList<String[]>(lines.length - 1);
for (int i = 1; i < lines.length; i++) {
@@ -1199,7 +1201,8 @@
String.format(
"device output: '%s' \nline: '%s' was not in the expected "
+ "format for user info.",
- commandOutput, lines[i]));
+ commandOutput, lines[i]),
+ DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
users.add(tokens);
}
diff --git a/src/com/android/tradefed/device/cloud/GceManager.java b/src/com/android/tradefed/device/cloud/GceManager.java
index 58fd31d..a206884 100644
--- a/src/com/android/tradefed/device/cloud/GceManager.java
+++ b/src/com/android/tradefed/device/cloud/GceManager.java
@@ -26,6 +26,7 @@
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.CommandResult;
@@ -218,7 +219,9 @@
+ "The instance may not have booted up at all.";
CLog.e(errors);
throw new TargetSetupError(
- String.format("acloud errors: %s", errors), mDeviceDescriptor);
+ String.format("acloud errors: %s", errors),
+ mDeviceDescriptor,
+ InfraErrorIdentifier.NO_ACLOUD_REPORT);
}
}
mGceAvdInfo =
@@ -226,7 +229,11 @@
reportFile, mDeviceDescriptor, mDeviceOptions.getRemoteAdbPort());
return mGceAvdInfo;
} catch (IOException e) {
- throw new TargetSetupError("failed to create log file", e, mDeviceDescriptor);
+ throw new TargetSetupError(
+ "failed to create log file",
+ e,
+ mDeviceDescriptor,
+ InfraErrorIdentifier.FAIL_TO_CREATE_FILE);
} finally {
FileUtil.deleteFile(reportFile);
}
diff --git a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
index 4925bb6..878e114 100644
--- a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
+++ b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
@@ -37,6 +37,8 @@
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.StreamUtil;
@@ -63,6 +65,7 @@
private GceManager mGceHandler = null;
private GceSshTunnelMonitor mGceSshMonitor;
+ private DeviceNotAvailableException mTunnelInitFailed = null;
private static final long WAIT_FOR_TUNNEL_ONLINE = 2 * 60 * 1000;
private static final long WAIT_AFTER_REBOOT = 60 * 1000;
@@ -91,6 +94,7 @@
try {
mGceAvd = null;
mGceSshMonitor = null;
+ mTunnelInitFailed = null;
// We create a brand new GceManager each time to ensure clean state.
mGceHandler = new GceManager(getDeviceDescriptor(), getOptions(), info);
getGceHandler().logStableHostImageInfos(info);
@@ -269,7 +273,10 @@
String.format(
"Device failed to boot. Error from Acloud: %s",
mGceAvd.getErrors());
- throw new TargetSetupError(errorMsg, getDeviceDescriptor());
+ throw new TargetSetupError(
+ errorMsg,
+ getDeviceDescriptor(),
+ DeviceErrorIdentifier.FAILED_TO_LAUNCH_GCE);
}
}
createGceSshMonitor(this, buildInfo, mGceAvd.hostAndPort(), this.getOptions());
@@ -322,14 +329,20 @@
}
getRunUtil().sleep(RETRY_INTERVAL_MS);
}
- throw new DeviceNotAvailableException(
- String.format("Tunnel did not come back online after %sms", waitTime),
- getSerialNumber(),
- DeviceErrorIdentifier.FAILED_TO_CONNECT_TO_GCE);
+ mTunnelInitFailed =
+ new DeviceNotAvailableException(
+ String.format("Tunnel did not come back online after %sms", waitTime),
+ getSerialNumber(),
+ DeviceErrorIdentifier.FAILED_TO_CONNECT_TO_GCE);
+ throw mTunnelInitFailed;
}
@Override
public void recoverDevice() throws DeviceNotAvailableException {
+ if (getGceSshMonitor() == null && mTunnelInitFailed != null) {
+ // We threw before but was not reported, so throw the root cause here.
+ throw mTunnelInitFailed;
+ }
// Re-init tunnel when attempting recovery
CLog.i("Attempting recovery on GCE AVD %s", getSerialNumber());
getGceSshMonitor().closeConnection();
@@ -447,4 +460,38 @@
}
return descriptor;
}
+
+ /**
+ * Attempt to powerwash a GCE instance
+ *
+ * @return returns true if powerwash Gce success.
+ * @throws TargetSetupError
+ * @throws DeviceNotAvailableException
+ */
+ public boolean powerwashGce() throws TargetSetupError, DeviceNotAvailableException {
+ if (mGceAvd == null) {
+ String errorMsg = String.format("Can not get GCE AVD Info. launch GCE first?");
+ throw new TargetSetupError(
+ errorMsg, getDeviceDescriptor(), DeviceErrorIdentifier.DEVICE_UNAVAILABLE);
+ }
+ String username = this.getOptions().getInstanceUser();
+ String powerwashCommand = String.format("/home/%s/bin/powerwash_cvd", username);
+ CommandResult powerwashRes =
+ GceManager.remoteSshCommandExecution(
+ mGceAvd,
+ this.getOptions(),
+ getRunUtil(),
+ 60000L,
+ powerwashCommand.split(" "));
+ if (!CommandStatus.SUCCESS.equals(powerwashRes.getStatus())) {
+ CLog.e("%s", powerwashRes.getStderr());
+ // Log 'adb devices' to confirm device is gone
+ CommandResult printAdbDevices = getRunUtil().runTimedCmd(60000L, "adb", "devices");
+ CLog.e("%s\n%s", printAdbDevices.getStdout(), printAdbDevices.getStderr());
+ // Proceed here, device could have been already gone.
+ return false;
+ }
+ getMonitor().waitForDeviceAvailable();
+ return true;
+ }
}
diff --git a/src/com/android/tradefed/device/metric/AutoLogCollector.java b/src/com/android/tradefed/device/metric/AutoLogCollector.java
index 41bbfdb..d93b377 100644
--- a/src/com/android/tradefed/device/metric/AutoLogCollector.java
+++ b/src/com/android/tradefed/device/metric/AutoLogCollector.java
@@ -20,7 +20,10 @@
/** Enumeration describing which collector can automatically be handled by the harness. */
public enum AutoLogCollector {
BUGREPORTZ_ON_FAILURE(BugreportzOnFailureCollector.class),
+ CLANG_COVERAGE(ClangCodeCoverageCollector.class),
+ GCOV_COVERAGE(GcovCodeCoverageCollector.class),
HOSTLOG_ON_FAILURE(DebugHostLogOnFailureCollector.class),
+ JAVA_COVERAGE(JavaCodeCoverageCollector.class),
LOGCAT_ON_FAILURE(LogcatOnFailureCollector.class),
SCREENSHOT_ON_FAILURE(ScreenshotOnFailureCollector.class);
diff --git a/test_framework/com/android/tradefed/testtype/ClangCodeCoverageListener.java b/src/com/android/tradefed/device/metric/ClangCodeCoverageCollector.java
similarity index 73%
rename from test_framework/com/android/tradefed/testtype/ClangCodeCoverageListener.java
rename to src/com/android/tradefed/device/metric/ClangCodeCoverageCollector.java
index b0fb295..9e6304a 100644
--- a/test_framework/com/android/tradefed/testtype/ClangCodeCoverageListener.java
+++ b/src/com/android/tradefed/device/metric/ClangCodeCoverageCollector.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.tradefed.testtype;
+package com.android.tradefed.device.metric;
-import static com.google.common.base.Verify.verify;
+import static com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain.CLANG;
import static com.google.common.base.Verify.verifyNotNull;
import com.android.tradefed.build.BuildRetrievalError;
@@ -25,14 +25,13 @@
import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.result.ResultForwarder;
-import com.android.tradefed.testtype.coverage.CoverageOptions;
-import com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain;
+import com.android.tradefed.util.AdbRootElevator;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
@@ -48,15 +47,15 @@
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
- * A {@link ResultForwarder} that will pull Clang coverage measurements off of the device and log
- * them as test artifacts.
+ * A {@link com.android.tradefed.device.metric.BaseDeviceMetricCollector} that will pull Clang
+ * coverage measurements off of the device and log them as test artifacts.
*/
-public final class ClangCodeCoverageListener extends ResultForwarder
+public final class ClangCodeCoverageCollector extends BaseDeviceMetricCollector
implements IConfigurationReceiver {
private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace";
@@ -74,21 +73,27 @@
private static final String DELETE_COVERAGE_FILES_COMMAND =
String.format("find %s -name '*.profraw' -delete", NATIVE_COVERAGE_DEVICE_PATH);
- private final ITestDevice mDevice;
-
private IBuildInfo mBuildInfo;
private IConfiguration mConfiguration;
- private IRunUtil mRunUtil;
+ private IRunUtil mRunUtil = RunUtil.getDefault();
private NativeCodeCoverageFlusher mFlusher;
- private File mLlvmProfdataTool;
- private String mCurrentRunName;
+ @Override
+ public ITestInvocationListener init(
+ IInvocationContext context, ITestInvocationListener listener) {
+ super.init(context, listener);
- public ClangCodeCoverageListener(ITestDevice device, ITestInvocationListener... listeners) {
- super(listeners);
- mDevice = device;
- mRunUtil = RunUtil.getDefault();
+ if (isClangCoverageEnabled()) {
+ // Clear coverage measurements on the device.
+ try (AdbRootElevator adbRoot = new AdbRootElevator(getDevices().get(0))) {
+ getCoverageFlusher().resetCoverage();
+ } catch (DeviceNotAvailableException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return this;
}
@Override
@@ -102,42 +107,26 @@
}
@Override
- public void testRunStarted(String runName, int testCount) {
- mCurrentRunName = runName;
- super.testRunStarted(runName, testCount);
- }
+ public void onTestRunEnd(
+ DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) {
+ if (!isClangCoverageEnabled()) {
+ return;
+ }
- @Override
- public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
- CoverageOptions options = mConfiguration.getCoverageOptions();
- try {
- if (options.isCoverageEnabled()
- && options.getCoverageToolchains().contains(Toolchain.CLANG)) {
- // Enable abd root on the device, otherwise the following commands will fail.
- verify(mDevice.enableAdbRoot(), "Failed to enable adb root.");
-
- if (options.isCoverageFlushEnabled()) {
- getCoverageFlusher().forceCoverageFlush();
- }
- logCoverageMeasurement(mCurrentRunName);
-
- // Delete coverage files on the device.
- mDevice.executeShellCommand(DELETE_COVERAGE_FILES_COMMAND);
+ ITestDevice device = getRealDevices().get(0);
+ try (AdbRootElevator adbRoot = new AdbRootElevator(device)) {
+ if (mConfiguration.getCoverageOptions().isCoverageFlushEnabled()) {
+ getCoverageFlusher().forceCoverageFlush();
}
+ logCoverageMeasurement(device, getRunName());
+
+ // Delete coverage files on the device.
+ device.executeShellCommand(DELETE_COVERAGE_FILES_COMMAND);
} catch (DeviceNotAvailableException | IOException e) {
throw new RuntimeException(e);
- } finally {
- super.testRunEnded(elapsedTime, runMetrics);
}
}
- @Override
- public void invocationEnded(long elapsedTime) {
- // Clean up the llvm-profdata tool.
- FileUtil.recursiveDelete(mLlvmProfdataTool);
- super.invocationEnded(elapsedTime);
- }
-
/**
* Logs Clang coverage measurements from the device.
*
@@ -145,7 +134,7 @@
* @throws DeviceNotAvailableException
* @throws IOException
*/
- private void logCoverageMeasurement(String runName)
+ private void logCoverageMeasurement(ITestDevice device, String runName)
throws DeviceNotAvailableException, IOException {
File coverageTarGz = null;
File untarDir = null;
@@ -153,13 +142,13 @@
File indexedProfileFile = null;
try {
// Compress coverage measurements on the device before pulling.
- mDevice.executeShellCommand(ZIP_CLANG_FILES_COMMAND);
- coverageTarGz = mDevice.pullFile(COVERAGE_TAR_PATH);
+ device.executeShellCommand(ZIP_CLANG_FILES_COMMAND);
+ coverageTarGz = device.pullFile(COVERAGE_TAR_PATH);
verifyNotNull(
coverageTarGz,
"Failed to pull the Clang code coverage file %s",
COVERAGE_TAR_PATH);
- mDevice.deleteFile(COVERAGE_TAR_PATH);
+ device.deleteFile(COVERAGE_TAR_PATH);
untarDir = FileUtil.createTempDir("clang_coverage");
TarUtil.unTar(coverageTarGz, untarDir);
@@ -208,6 +197,7 @@
} finally {
FileUtil.deleteFile(coverageTarGz);
FileUtil.recursiveDelete(untarDir);
+ FileUtil.recursiveDelete(profileTool);
FileUtil.deleteFile(indexedProfileFile);
}
}
@@ -220,25 +210,26 @@
private NativeCodeCoverageFlusher getCoverageFlusher() {
if (mFlusher == null) {
verifyNotNull(mConfiguration);
- verifyNotNull(mDevice);
mFlusher =
new NativeCodeCoverageFlusher(
- mDevice, mConfiguration.getCoverageOptions().getCoverageProcesses());
+ getDevices().get(0),
+ mConfiguration.getCoverageOptions().getCoverageProcesses());
}
return mFlusher;
}
+ private boolean isClangCoverageEnabled() {
+ return mConfiguration != null
+ && mConfiguration.getCoverageOptions().isCoverageEnabled()
+ && mConfiguration.getCoverageOptions().getCoverageToolchains().contains(CLANG);
+ }
+
/**
* Retrieves the profile tool and dependencies from the build, and extracts them.
*
* @return the directory containing the profile tool and dependencies
*/
private File getProfileTool() throws IOException {
- // If we have a cached version of the profile tool already, use it.
- if (mLlvmProfdataTool != null) {
- return mLlvmProfdataTool;
- }
-
// If llvm-profdata-path was set in the Configuration, pass it through. Don't save the path
// locally since the parent process is responsible for cleaning it up.
File configurationTool = mConfiguration.getCoverageOptions().getLlvmProfdataPath();
@@ -254,8 +245,7 @@
verifyNotNull(
buildInfo.getFile("llvm-profdata.zip"),
"Could not get llvm-profdata.zip from the build.");
- mLlvmProfdataTool = ZipUtil.extractZipToTemp(profileToolZip, "llvm-profdata");
- return mLlvmProfdataTool;
+ return ZipUtil.extractZipToTemp(profileToolZip, "llvm-profdata");
} catch (BuildRetrievalError e) {
throw new RuntimeException(e);
} finally {
diff --git a/src/com/android/tradefed/device/metric/FilePullerLogCollector.java b/src/com/android/tradefed/device/metric/FilePullerLogCollector.java
index 75cb203..da8c240 100644
--- a/src/com/android/tradefed/device/metric/FilePullerLogCollector.java
+++ b/src/com/android/tradefed/device/metric/FilePullerLogCollector.java
@@ -42,9 +42,12 @@
String ext = FileUtil.getExtension(metricFile.getName()).toLowerCase();
if (".png".equals(ext)) {
type = LogDataType.PNG;
- }
- if (".pb".equals(ext)) {
+ } else if (".pb".equals(ext)) {
type = LogDataType.PB;
+ } else if (".mp4".equals(ext)) {
+ type = LogDataType.MP4;
+ } else if (".hprof".equals(ext)) {
+ type = LogDataType.HPROF;
}
testLog(metricFile.getName(), type, source);
}
diff --git a/src/com/android/tradefed/device/metric/GcovCodeCoverageCollector.java b/src/com/android/tradefed/device/metric/GcovCodeCoverageCollector.java
new file mode 100644
index 0000000..0bd59e2
--- /dev/null
+++ b/src/com/android/tradefed/device/metric/GcovCodeCoverageCollector.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2019 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.device.metric;
+
+import static com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain.GCOV;
+import static com.google.common.base.Verify.verifyNotNull;
+
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.FileInputStreamSource;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.util.AdbRootElevator;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.NativeCodeCoverageFlusher;
+import com.android.tradefed.util.TarUtil;
+import com.android.tradefed.util.ZipUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * A {@link com.android.tradefed.device.metric.BaseDeviceMetricCollector} that will pull gcov
+ * coverage measurements off of the device and log them as test artifacts.
+ */
+public final class GcovCodeCoverageCollector extends BaseDeviceMetricCollector
+ implements IConfigurationReceiver {
+
+ private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace";
+ private static final String COVERAGE_TAR_PATH =
+ String.format("%s/coverage.tar", NATIVE_COVERAGE_DEVICE_PATH);
+
+ // Finds .gcda files in /data/misc/trace and compresses those files only. Stores the full
+ // path of the file on the device.
+ private static final String ZIP_COVERAGE_FILES_COMMAND =
+ String.format(
+ "find %s -name '*.gcda' | tar -cvf %s -T -",
+ NATIVE_COVERAGE_DEVICE_PATH, COVERAGE_TAR_PATH);
+
+ // Deletes .gcda files in /data/misc/trace.
+ private static final String DELETE_COVERAGE_FILES_COMMAND =
+ String.format("find %s -name '*.gcda' -delete", NATIVE_COVERAGE_DEVICE_PATH);
+
+ private NativeCodeCoverageFlusher mFlusher;
+ private boolean mCollectCoverageOnTestEnd = true;
+ private IConfiguration mConfiguration;
+
+ @Override
+ public ITestInvocationListener init(
+ IInvocationContext context, ITestInvocationListener listener) {
+ super.init(context, listener);
+
+ if (isGcovCoverageEnabled()) {
+ // Clear coverage measurements on the device.
+ try (AdbRootElevator adbRoot = new AdbRootElevator(getDevices().get(0))) {
+ getCoverageFlusher().resetCoverage();
+ } catch (DeviceNotAvailableException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return this;
+ }
+
+ @Override
+ public void setConfiguration(IConfiguration config) {
+ mConfiguration = config;
+ }
+
+ private boolean isGcovCoverageEnabled() {
+ return mConfiguration != null
+ && mConfiguration.getCoverageOptions().isCoverageEnabled()
+ && mConfiguration.getCoverageOptions().getCoverageToolchains().contains(GCOV);
+ }
+
+ private NativeCodeCoverageFlusher getCoverageFlusher() {
+ if (mFlusher == null) {
+ mFlusher =
+ new NativeCodeCoverageFlusher(
+ getDevices().get(0),
+ mConfiguration.getCoverageOptions().getCoverageProcesses());
+ }
+ return mFlusher;
+ }
+
+ /**
+ * Sets whether to collect coverage on testRunEnded.
+ *
+ * <p>Set this to false during re-runs, otherwise each individual test re-run will collect
+ * coverage rather than having a single merged coverage result.
+ */
+ public void setCollectOnTestEnd(boolean collect) {
+ mCollectCoverageOnTestEnd = collect;
+ }
+
+ @Override
+ public void onTestRunEnd(DeviceMetricData runData, final Map<String, Metric> runMetrics) {
+ if (!isGcovCoverageEnabled()) {
+ return;
+ }
+
+ if (mCollectCoverageOnTestEnd) {
+ logCoverageMeasurements(getRunName());
+ }
+ }
+
+ /** Pulls native coverage measurements from the device and logs them. */
+ public void logCoverageMeasurements(String runName) {
+ File coverageTar = null;
+ File coverageZip = null;
+ ITestDevice device = getRealDevices().get(0);
+
+ // Enable abd root on the device, otherwise the following commands will fail.
+ try (AdbRootElevator adbRoot = new AdbRootElevator(device)) {
+ // Flush cross-process coverage.
+ if (mConfiguration.getCoverageOptions().isCoverageFlushEnabled()) {
+ getCoverageFlusher().forceCoverageFlush();
+ }
+
+ // Compress coverage measurements on the device before pulling.
+ device.executeShellCommand(ZIP_COVERAGE_FILES_COMMAND);
+ coverageTar = device.pullFile(COVERAGE_TAR_PATH);
+ verifyNotNull(
+ coverageTar,
+ "Failed to pull the native code coverage file %s",
+ COVERAGE_TAR_PATH);
+ device.deleteFile(COVERAGE_TAR_PATH);
+
+ coverageZip = convertTarToZip(coverageTar);
+
+ try (FileInputStreamSource source = new FileInputStreamSource(coverageZip, true)) {
+ testLog(runName + "_native_runtime_coverage", LogDataType.NATIVE_COVERAGE, source);
+ }
+
+ // Delete coverage files on the device.
+ device.executeShellCommand(DELETE_COVERAGE_FILES_COMMAND);
+ } catch (DeviceNotAvailableException | IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ FileUtil.deleteFile(coverageTar);
+ FileUtil.deleteFile(coverageZip);
+ }
+ }
+
+ /**
+ * Converts a .tar file to a .zip file.
+ *
+ * @param tar the .tar file to convert
+ * @return a .zip file with the same contents
+ * @throws IOException
+ */
+ private File convertTarToZip(File tar) throws IOException {
+ File untarDir = null;
+ try {
+ untarDir = FileUtil.createTempDir("gcov_coverage");
+ TarUtil.unTar(tar, untarDir);
+ return ZipUtil.createZip(Arrays.asList(untarDir.listFiles()), "native_coverage");
+ } finally {
+ FileUtil.recursiveDelete(untarDir);
+ }
+ }
+}
diff --git a/src/com/android/tradefed/device/metric/IncidentReportCollector.java b/src/com/android/tradefed/device/metric/IncidentReportCollector.java
index f6246fc..a225e86 100644
--- a/src/com/android/tradefed/device/metric/IncidentReportCollector.java
+++ b/src/com/android/tradefed/device/metric/IncidentReportCollector.java
@@ -18,19 +18,30 @@
import android.os.IncidentProto;
+import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ByteArrayInputStreamSource;
+import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
+import java.util.Map;
/**
- * Pulls and processes incident reports that are reported device-side.
- *
- * <p>TODO(b/119418529): Collect an incident report host-side on failure.
+ * Pulls and processes incident reports that are reported device-side and collects incident reports
+ * host-side at the end of a test run if configured to do so.
*/
@OptionClass(alias = "incident-collector")
public class IncidentReportCollector extends FilePullerLogCollector {
@@ -38,13 +49,68 @@
private static final String INCIDENT_KEY_MATCHER = "incident-report";
// Suffix for all of the logs that are processed incident reports.
private static final String PROCESSED_KEY_SUFFIX = "-processed";
+ // Incident report command and associated timeouts that are used.
+ static final String INCIDENT_REPORT_CMD = "incident -b -p EXPLICIT";
+ private static final long INCIDENT_DUMP_TIMEOUT = 5 * 60 * 1000;
+ private static final long DEVICE_AVAILABLE_TIMEOUT = 10 * 60 * 1000;
+
+ @Option(
+ name = "incident-on-test-run-end",
+ description =
+ "Collect an incident report at the end of each test run. This report will not"
+ + " be collected if a report was collected on-device and logged prior.")
+ private boolean mIncidentOnRunEnd = false;
public IncidentReportCollector() {
addKeys(INCIDENT_KEY_MATCHER);
}
@Override
+ public void onTestRunEnd(
+ DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) {
+ super.onTestRunEnd(runData, currentRunMetrics);
+ // Only collect if set and there wasn't an on-device report collected.
+ if (!mIncidentOnRunEnd) {
+ return;
+ }
+ for (ITestDevice device : getDevices()) {
+ File outFile = null;
+ try {
+ // Ensure the device is available for the command.
+ device.waitForDeviceAvailable(DEVICE_AVAILABLE_TIMEOUT);
+ // Collect the incident report to a new file.
+ outFile = File.createTempFile("incident-on-test-run-end", ".pb");
+ FileOutputStream outStream = new FileOutputStream(outFile, false);
+ CommandResult result = device.executeShellV2Command(INCIDENT_REPORT_CMD, outStream);
+ // Complain (and say why) if something didn't go right.
+ if (result.getStatus().equals(CommandStatus.SUCCESS)) {
+ // Log the extra file and process it as a report.
+ try (InputStreamSource source = new FileInputStreamSource(outFile)) {
+ testLog(outFile.getName(), LogDataType.PB, source);
+ }
+ logProcessedReport(outFile);
+ } else {
+ CLog.e(
+ "There was an error collecting an incident report: %s",
+ result.getStderr());
+ }
+ // Just wrap exceptions and print them.
+ } catch (DeviceNotAvailableException dnae) {
+ CLog.e(dnae);
+ } catch (IOException ioe) {
+ CLog.e(ioe);
+ } finally {
+ FileUtil.deleteFile(outFile);
+ }
+ }
+ }
+
+ @Override
protected void postProcessMetricFile(String key, File metricFile, DeviceMetricData runData) {
+ logProcessedReport(metricFile);
+ }
+
+ private void logProcessedReport(File metricFile) {
// Read and interpret the incident report's bytes.
IncidentProto processedReport;
try {
diff --git a/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java b/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java
new file mode 100644
index 0000000..d04b1ca
--- /dev/null
+++ b/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java
@@ -0,0 +1,245 @@
+/*
+ * 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.device.metric;
+
+import static com.google.common.base.Verify.verifyNotNull;
+import static com.google.common.io.Files.getNameWithoutExtension;
+
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.logger.CurrentInvocation;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.FailureDescription;
+import com.android.tradefed.result.FileInputStreamSource;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
+import com.android.tradefed.testtype.coverage.CoverageOptions;
+import com.android.tradefed.util.AdbRootElevator;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.JavaCodeCoverageFlusher;
+import com.android.tradefed.util.ProcessInfo;
+import com.android.tradefed.util.PsParser;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+
+import org.jacoco.core.tools.ExecFileLoader;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.List;
+
+/**
+ * A {@link com.android.tradefed.device.metric.BaseDeviceMetricCollector} that will pull Java
+ * coverage measurements off of the device and log them as test artifacts.
+ */
+public final class JavaCodeCoverageCollector extends BaseDeviceMetricCollector
+ implements IConfigurationReceiver {
+
+ public static final String MERGE_COVERAGE_MEASUREMENTS_TEST_NAME = "mergeCoverageMeasurements";
+ public static final String COVERAGE_MEASUREMENT_KEY = "coverageFilePath";
+ public static final String COVERAGE_DIRECTORY = "/data/misc/trace";
+ public static final String FIND_COVERAGE_FILES =
+ String.format("find %s -name '*.ec'", COVERAGE_DIRECTORY);
+
+ @Option(
+ name = "merge-coverage-measurements",
+ description =
+ "Merge coverage measurements after all tests are complete rather than logging individual measurements.")
+ private boolean mMergeCoverageMeasurements = false;
+
+ private final ExecFileLoader mExecFileLoader = new ExecFileLoader();
+
+ private JavaCodeCoverageFlusher mFlusher;
+ private IConfiguration mConfiguration;
+
+ @Override
+ public ITestInvocationListener init(
+ IInvocationContext context, ITestInvocationListener listener) {
+ super.init(context, listener);
+
+ if (isJavaCoverageEnabled()) {
+ try (AdbRootElevator adbRoot = new AdbRootElevator(getDevices().get(0))) {
+ getCoverageFlusher().resetCoverage();
+ } catch (DeviceNotAvailableException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return this;
+ }
+
+ @Override
+ public void setConfiguration(IConfiguration configuration) {
+ mConfiguration = configuration;
+ }
+
+ private JavaCodeCoverageFlusher getCoverageFlusher() {
+ if (mFlusher == null) {
+ mFlusher =
+ new JavaCodeCoverageFlusher(
+ getRealDevices().get(0),
+ mConfiguration.getCoverageOptions().getCoverageProcesses());
+ }
+ return mFlusher;
+ }
+
+ @VisibleForTesting
+ public void setCoverageFlusher(JavaCodeCoverageFlusher flusher) {
+ mFlusher = flusher;
+ }
+
+ @VisibleForTesting
+ public void setMergeMeasurements(boolean merge) {
+ mMergeCoverageMeasurements = merge;
+ }
+
+ @Override
+ public void onTestRunEnd(DeviceMetricData runData, final Map<String, Metric> runMetrics) {
+ if (!isJavaCoverageEnabled()) {
+ return;
+ }
+ if (MERGE_COVERAGE_MEASUREMENTS_TEST_NAME.equals(getRunName())) {
+ // Log the merged runtime coverage measurement.
+ try {
+ File mergedMeasurements =
+ FileUtil.createTempFile(
+ "merged_runtime_coverage_",
+ "." + LogDataType.COVERAGE.getFileExt());
+
+ mExecFileLoader.save(mergedMeasurements, false);
+
+ // Save the merged measurement as a test log.
+ logCoverageMeasurement("merged_runtime_coverage", mergedMeasurements);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ // Get the path of the coverage measurement on the device.
+ Metric devicePathMetric = runMetrics.get(COVERAGE_MEASUREMENT_KEY);
+ if (devicePathMetric == null) {
+ super.testRunFailed(
+ createCodeCoverageFailure("No Java code coverage measurement."));
+ return;
+ }
+ String testCoveragePath = devicePathMetric.getMeasurements().getSingleString();
+ if (testCoveragePath == null) {
+ super.testRunFailed(
+ createCodeCoverageFailure("No Java code coverage measurement."));
+ return;
+ }
+
+ ITestDevice device = getRealDevices().get(0);
+ ImmutableList.Builder<String> devicePaths = ImmutableList.builder();
+ devicePaths.add(testCoveragePath);
+
+ try (AdbRootElevator adbRoot = new AdbRootElevator(device)) {
+ if (mConfiguration.getCoverageOptions().isCoverageFlushEnabled()) {
+ getCoverageFlusher().forceCoverageFlush();
+ }
+
+ // Find all .ec files in /data/misc/trace and pull them from the device as well.
+ String fileList = device.executeShellCommand(FIND_COVERAGE_FILES);
+ devicePaths.addAll(Splitter.on('\n').omitEmptyStrings().split(fileList));
+
+ collectAndLogCoverageMeasurements(device, devicePaths.build());
+ } catch (DeviceNotAvailableException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private void logCoverageMeasurement(String name, File coverageFile) {
+ try (FileInputStreamSource source = new FileInputStreamSource(coverageFile, true)) {
+ testLog(name, LogDataType.COVERAGE, source);
+ }
+ }
+
+ private void collectAndLogCoverageMeasurements(ITestDevice device, List<String> devicePaths)
+ throws IOException, DeviceNotAvailableException {
+ List<Integer> activePids = getRunningProcessIds(device);
+
+ for (String devicePath : devicePaths) {
+ File coverageFile = device.pullFile(devicePath);
+
+ if (devicePath.endsWith(".mm.ec")) {
+ // Check if the process was still running. The file will have the format
+ // /data/misc/trace/jacoco-XXXXX.mm.ec where XXXXX is the process id.
+ int start = devicePath.indexOf('-') + 1;
+ int end = devicePath.indexOf('.');
+ int pid = Integer.parseInt(devicePath.substring(start, end));
+ if (!activePids.contains(pid)) {
+ device.deleteFile(devicePath);
+ }
+ } else {
+ device.deleteFile(devicePath);
+ }
+
+ verifyNotNull(
+ coverageFile, "Failed to pull the Java code coverage file from %s", devicePath);
+
+ // When merging, load the measurement data. Otherwise log the measurement
+ // immediately.
+ try {
+ if (mMergeCoverageMeasurements) {
+ mExecFileLoader.load(coverageFile);
+ } else {
+ logCoverageMeasurement(
+ getRunName()
+ + "_"
+ + getNameWithoutExtension(devicePath)
+ + "_runtime_coverage",
+ coverageFile);
+ }
+ } finally {
+ FileUtil.deleteFile(coverageFile);
+ }
+ }
+ }
+
+ private List<Integer> getRunningProcessIds(ITestDevice device)
+ throws DeviceNotAvailableException {
+ List<ProcessInfo> processes = PsParser.getProcesses(device.executeShellCommand("ps -e"));
+ List<Integer> pids = new ArrayList<>();
+
+ for (ProcessInfo process : processes) {
+ pids.add(process.getPid());
+ }
+ return pids;
+ }
+
+ private FailureDescription createCodeCoverageFailure(String message) {
+ return CurrentInvocation.createFailure(message, InfraErrorIdentifier.CODE_COVERAGE_ERROR);
+ }
+
+ private boolean isJavaCoverageEnabled() {
+ return mConfiguration != null
+ && mConfiguration.getCoverageOptions().isCoverageEnabled()
+ && mConfiguration
+ .getCoverageOptions()
+ .getCoverageToolchains()
+ .contains(CoverageOptions.Toolchain.JACOCO);
+ }
+}
diff --git a/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java b/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java
index c4c9697..56292d2 100644
--- a/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java
+++ b/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java
@@ -24,6 +24,7 @@
import com.android.tradefed.error.HarnessRuntimeException;
import com.android.tradefed.invoker.TestInvocation.Stage;
import com.android.tradefed.log.ITestLogger;
+import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
@@ -131,6 +132,7 @@
IRunUtil runUtil = createRunUtil(receiver.getSocketServerPort());
CommandResult result = null;
RuntimeException runtimeException = null;
+ CLog.d("Command line: %s", commandLine);
try {
result =
runUtil.runTimedCmd(
@@ -159,7 +161,7 @@
}
if (result.getStatus().equals(CommandStatus.TIMED_OUT)) {
throw new HarnessRuntimeException(
- "Delegated invocation timed out.", InfraErrorIdentifier.UNDETERMINED);
+ "Delegated invocation timed out.", InfraErrorIdentifier.INVOCATION_TIMEOUT);
}
} finally {
StreamUtil.close(mStderr);
diff --git a/src/com/android/tradefed/invoker/InvocationExecution.java b/src/com/android/tradefed/invoker/InvocationExecution.java
index 8e522a9..7a4ae8a 100644
--- a/src/com/android/tradefed/invoker/InvocationExecution.java
+++ b/src/com/android/tradefed/invoker/InvocationExecution.java
@@ -27,6 +27,7 @@
import com.android.tradefed.build.IDeviceBuildProvider;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.config.IDeviceConfiguration;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
@@ -82,6 +83,7 @@
import java.io.File;
import java.io.IOException;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -230,11 +232,12 @@
mTrackTargetPreparers = new ConcurrentHashMap<>();
int index = 0;
- if (config.getCommandOptions().shouldUseReplicateSetup()
+ if ((config.getCommandOptions().shouldUseParallelSetup()
+ || config.getCommandOptions().shouldUseReplicateSetup())
&& config.getDeviceConfig().size() > 1) {
- CLog.d("Using parallel setup due to replicated setup enabled.");
+ CLog.d("Using parallel setup.");
ParallelDeviceExecutor<Boolean> executor =
- new ParallelDeviceExecutor<>(testInfo.getContext().getDevices());
+ new ParallelDeviceExecutor<>(testInfo.getContext().getDevices().size());
List<Callable<Boolean>> callableTasks = new ArrayList<>();
for (String deviceName : testInfo.getContext().getDeviceConfigNames()) {
mTrackTargetPreparers.put(deviceName, new HashSet<>());
@@ -251,8 +254,8 @@
callableTasks.add(callableTask);
index++;
}
- // Run setup with 30 minutes right now.
- executor.invokeAll(callableTasks, 30, TimeUnit.MINUTES);
+ Duration timeout = config.getCommandOptions().getParallelSetupTimeout();
+ executor.invokeAll(callableTasks, timeout.toMillis(), TimeUnit.MILLISECONDS);
if (executor.hasErrors()) {
List<Throwable> errors = executor.getErrors();
// TODO: Handle throwing multi-exceptions, right now throw the first one.
@@ -745,6 +748,9 @@
if (collector.isDisabled()) {
CLog.d("%s has been disabled. Skipping.", collector);
} else {
+ if (collector instanceof IConfigurationReceiver) {
+ ((IConfigurationReceiver) collector).setConfiguration(config);
+ }
listenerWithCollectors =
collector.init(info.getContext(), listenerWithCollectors);
TfObjectTracker.countWithParents(collector.getClass());
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index 2927f19..c4ffc43 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -35,6 +35,7 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.device.NativeDevice;
+import com.android.tradefed.device.RemoteAndroidDevice;
import com.android.tradefed.device.StubDevice;
import com.android.tradefed.device.TcpDevice;
import com.android.tradefed.device.TestDeviceState;
@@ -42,6 +43,7 @@
import com.android.tradefed.device.cloud.NestedRemoteDevice;
import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
import com.android.tradefed.error.HarnessException;
+import com.android.tradefed.error.HarnessRuntimeException;
import com.android.tradefed.error.IHarnessException;
import com.android.tradefed.guice.InvocationScope;
import com.android.tradefed.invoker.logger.CurrentInvocation;
@@ -163,6 +165,7 @@
private Long mStopRequestTime = null;
private boolean mTestStarted = false;
private boolean mInvocationFailed = false;
+ private boolean mDelegatedInvocation = false;
private List<IScheduledInvocationListener> mSchedulerListeners = new ArrayList<>();
/**
@@ -213,7 +216,6 @@
throws Throwable {
ReportHostLog reportThread = new ReportHostLog(listener, config);
Runtime.getRuntime().addShutdownHook(reportThread);
- boolean resumed = false;
String bugreportName = null;
long startTime = System.currentTimeMillis();
long elapsedTime = -1;
@@ -305,37 +307,39 @@
}
CurrentInvocation.setActionInProgress(ActionInProgress.TEAR_DOWN);
getRunUtil().allowInterrupt(false);
- if (config.getCommandOptions().takeBugreportOnInvocationEnded() ||
- config.getCommandOptions().takeBugreportzOnInvocationEnded()) {
- if (bugreportName != null) {
- CLog.i("Bugreport to be taken for failure instead of invocation ended.");
- } else {
- bugreportName = INVOCATION_ENDED_BUGREPORT_NAME;
+ if (!mDelegatedInvocation) {
+ if (config.getCommandOptions().takeBugreportOnInvocationEnded()
+ || config.getCommandOptions().takeBugreportzOnInvocationEnded()) {
+ if (bugreportName != null) {
+ CLog.i("Bugreport to be taken for failure instead of invocation ended.");
+ } else {
+ bugreportName = INVOCATION_ENDED_BUGREPORT_NAME;
+ }
}
- }
- if (bugreportName != null) {
- if (context.getDevices().size() == 1 || badDevice != null) {
- ITestDevice collectBugreport = badDevice;
- if (collectBugreport == null) {
- collectBugreport = context.getDevices().get(0);
+ if (bugreportName != null) {
+ if (context.getDevices().size() == 1 || badDevice != null) {
+ ITestDevice collectBugreport = badDevice;
+ if (collectBugreport == null) {
+ collectBugreport = context.getDevices().get(0);
+ }
+ // If we have identified a faulty device only take the bugreport on it.
+ takeBugreport(collectBugreport, listener, bugreportName);
+ } else if (context.getDevices().size() > 1) {
+ ParallelDeviceExecutor<Boolean> executor =
+ new ParallelDeviceExecutor<>(context.getDevices().size());
+ List<Callable<Boolean>> callableTasks = new ArrayList<>();
+ final String reportName = bugreportName;
+ for (ITestDevice device : context.getDevices()) {
+ Callable<Boolean> callableTask =
+ () -> {
+ takeBugreport(device, listener, reportName);
+ return true;
+ };
+ callableTasks.add(callableTask);
+ }
+ // Capture the bugreports best effort, ignore the results.
+ executor.invokeAll(callableTasks, 5, TimeUnit.MINUTES);
}
- // If we have identified a faulty device only take the bugreport on it.
- takeBugreport(collectBugreport, listener, bugreportName);
- } else if (context.getDevices().size() > 1) {
- ParallelDeviceExecutor<Boolean> executor =
- new ParallelDeviceExecutor<>(context.getDevices());
- List<Callable<Boolean>> callableTasks = new ArrayList<>();
- final String reportName = bugreportName;
- for (ITestDevice device : context.getDevices()) {
- Callable<Boolean> callableTask =
- () -> {
- takeBugreport(device, listener, reportName);
- return true;
- };
- callableTasks.add(callableTask);
- }
- // Capture the bugreports best effort, ignore the results.
- executor.invokeAll(callableTasks, 5, TimeUnit.MINUTES);
}
}
// Save the device executeShellCommand logs
@@ -383,6 +387,10 @@
mStopCause);
FailureDescription failure =
FailureDescription.create(message, FailureStatus.CANCELLED);
+ failure.setErrorIdentifier(InfraErrorIdentifier.INVOCATION_CANCELLED);
+ failure.setCause(
+ new HarnessRuntimeException(
+ message, InfraErrorIdentifier.INVOCATION_CANCELLED));
reportFailure(failure, listener);
PrettyPrintDelimiter.printStageDelimiter(message);
if (mStopRequestTime != null) {
@@ -398,22 +406,7 @@
Runtime.getRuntime().removeShutdownHook(reportThread);
elapsedTime = System.currentTimeMillis() - startTime;
- if (!resumed) {
- // Init a log for the end of the host_log.
- ILeveledLogOutput endHostLog = config.getLogOutput();
- endHostLog.init();
- getLogRegistry().registerLogger(endHostLog);
- PrettyPrintDelimiter.printStageDelimiter("===== Result Reporters =====");
- try {
- // Copy the invocation metrics to the context
- ((InvocationContext) context).logInvocationMetrics();
- listener.invocationEnded(elapsedTime);
- } finally {
- InvocationMetricLogger.clearInvocationMetrics();
- endHostLog.closeLog();
- getLogRegistry().unregisterLogger();
- }
- }
+ reportInvocationEnded(config, context, listener, elapsedTime);
} finally {
TfObjectTracker.clearTracking();
CurrentInvocation.clearInvocationInfos();
@@ -497,7 +490,7 @@
private void reportHostLog(ITestInvocationListener listener, IConfiguration config) {
String name = TRADEFED_LOG_NAME;
- if (config.getConfigurationObject(TradefedDelegator.DELEGATE_OBJECT) != null) {
+ if (mDelegatedInvocation) {
name = TRADEFED_DELEGATED_LOG_NAME;
}
reportHostLog(listener, config, name);
@@ -649,7 +642,7 @@
invocationPath.reportLogs(device, listener, Stage.ERROR);
}
reportHostLog(listener, config);
- listener.invocationEnded(0L);
+ reportInvocationEnded(config, testInfo.getContext(), listener, 0L);
return false;
}
@@ -702,7 +695,7 @@
invocationPath.reportLogs(device, listener, Stage.ERROR);
}
reportHostLog(listener, config);
- listener.invocationEnded(0L);
+ reportInvocationEnded(config, context, listener, 0L);
return false;
}
}
@@ -796,6 +789,7 @@
mode = RunMode.REMOTE_INVOCATION;
}
if (config.getConfigurationObject(TradefedDelegator.DELEGATE_OBJECT) != null) {
+ mDelegatedInvocation = true;
mode = RunMode.DELEGATED_INVOCATION;
}
IInvocationExecution invocationPath = createInvocationExec(mode);
@@ -1128,7 +1122,8 @@
int countVirtualLost = 0;
for (Entry<ITestDevice, FreeDeviceState> fds : devicesStates.entrySet()) {
// TODO: Rely on the FailureStatus for lost devices instead
- if (fds.getKey().getIDevice() instanceof TcpDevice
+ if ((fds.getKey().getIDevice() instanceof TcpDevice
+ || fds.getKey() instanceof RemoteAndroidDevice)
&& exception instanceof DeviceNotAvailableException) {
countVirtualLost++;
continue;
@@ -1154,6 +1149,35 @@
return devicesStates;
}
+ private void reportInvocationEnded(
+ IConfiguration config,
+ IInvocationContext context,
+ ITestInvocationListener listener,
+ long elapsedTime) {
+ // Init a log for the end of the host_log.
+ ILeveledLogOutput endHostLog = config.getLogOutput();
+ try {
+ endHostLog.init();
+ getLogRegistry().registerLogger(endHostLog);
+ } catch (IOException e) {
+ CLog.e(e);
+ endHostLog = null;
+ }
+
+ PrettyPrintDelimiter.printStageDelimiter("===== Result Reporters =====");
+ try {
+ // Copy the invocation metrics to the context
+ ((InvocationContext) context).logInvocationMetrics();
+ listener.invocationEnded(elapsedTime);
+ } finally {
+ InvocationMetricLogger.clearInvocationMetrics();
+ if (endHostLog != null) {
+ endHostLog.closeLog();
+ getLogRegistry().unregisterLogger();
+ }
+ }
+ }
+
/** Helper Thread that ensures host_log is reported in case of killed JVM */
private class ReportHostLog extends Thread {
diff --git a/src/com/android/tradefed/invoker/shard/StrictShardHelper.java b/src/com/android/tradefed/invoker/shard/StrictShardHelper.java
index f3aeffc..cf30b05 100644
--- a/src/com/android/tradefed/invoker/shard/StrictShardHelper.java
+++ b/src/com/android/tradefed/invoker/shard/StrictShardHelper.java
@@ -35,7 +35,10 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/** Sharding strategy to create strict shards that do not report together, */
public class StrictShardHelper extends ShardHelper {
@@ -49,6 +52,7 @@
ITestLogger logger) {
Integer shardCount = config.getCommandOptions().getShardCount();
Integer shardIndex = config.getCommandOptions().getShardIndex();
+ boolean optimizeMainline = config.getCommandOptions().getOptimizeMainlineTest();
if (shardIndex == null) {
return super.shardConfig(config, testInfo, rescheduler, logger);
@@ -69,11 +73,50 @@
splitList = splitTests(listAllTests, shardCount).get(shardIndex);
}
aggregateSuiteModules(splitList);
+ if (optimizeMainline) {
+ CLog.i("Reordering the test modules list for index: %s", shardIndex);
+ reorderTestModules(splitList);
+ }
config.setTests(splitList);
return false;
}
/**
+ * Helper to re order the list full list of {@link IRemoteTest} for mainline.
+ *
+ * @param tests the {@link IRemoteTest} containing all the tests that need to run.
+ */
+ private void reorderTestModules(List<IRemoteTest> tests) {
+ Collections.sort(tests, new Comparator<IRemoteTest>() {
+ @Override
+ public int compare(IRemoteTest o1, IRemoteTest o2) {
+ String moduleId1 = ((ITestSuite)o1).getDirectModule().getId();
+ String moduleId2 = ((ITestSuite)o2).getDirectModule().getId();
+ return getMainlineId(moduleId1).compareTo(getMainlineId(moduleId2));
+ }
+ });
+ }
+
+ /**
+ * Returns the parameterized mainline modules' name defined in the square brackets.
+ *
+ * @param id The module's name.
+ * @throws RuntimeException if the module name doesn't match the pattern for mainline modules.
+ */
+ private String getMainlineId(String id) {
+ // Pattern used to identify the parameterized mainline modules defined in the square
+ // brackets.
+ Pattern parameterizedMainlineRegex = Pattern.compile("\\[(.*(\\.apk|.apex|.apks))\\]$");
+ Matcher m = parameterizedMainlineRegex.matcher(id);
+ if (m.find()) {
+ return m.group(1);
+ }
+ throw new RuntimeException(
+ String.format("Module: %s doesn't match the pattern for mainline modules. The " +
+ "pattern should end with apk/apex/apks.", id));
+ }
+
+ /**
* Helper to return the full list of {@link IRemoteTest} based on {@link IShardableTest} split.
*
* @param config the {@link IConfiguration} describing the invocation.
diff --git a/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java b/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java
index 666f19c..9efc9a6 100644
--- a/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java
+++ b/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java
@@ -178,6 +178,9 @@
try {
ITestInvocationListener listenerWithCollectors = listener;
for (IMetricCollector collector : mCollectors) {
+ if (collector instanceof IConfigurationReceiver) {
+ ((IConfigurationReceiver) collector).setConfiguration(mConfig);
+ }
listenerWithCollectors = collector.init(info.getContext(), listenerWithCollectors);
}
while (true) {
diff --git a/src/com/android/tradefed/monitoring/LabResourceDeviceMonitor.java b/src/com/android/tradefed/monitoring/LabResourceDeviceMonitor.java
new file mode 100644
index 0000000..8366e0e
--- /dev/null
+++ b/src/com/android/tradefed/monitoring/LabResourceDeviceMonitor.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 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.monitoring;
+
+import com.android.loganalysis.util.config.OptionClass;
+import com.android.tradefed.device.DeviceAllocationState;
+import com.android.tradefed.device.IDeviceMonitor;
+import com.android.tradefed.log.LogUtil;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.dualhomelab.monitoringagent.resourcemonitoring.LabResource;
+import com.google.dualhomelab.monitoringagent.resourcemonitoring.LabResourceRequest;
+import com.google.dualhomelab.monitoringagent.resourcemonitoring.LabResourceServiceGrpc;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Optional;
+import java.util.concurrent.Executors;
+
+import io.grpc.Server;
+import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
+import io.grpc.stub.StreamObserver;
+
+/** The lab resource monitor which initializes/manages the gRPC server for LabResourceService. */
+@OptionClass(alias = "lab-resource-monitor")
+public class LabResourceDeviceMonitor extends LabResourceServiceGrpc.LabResourceServiceImplBase
+ implements IDeviceMonitor {
+ public static final String SERVER_HOSTNAME = "localhost";
+ public static final int DEFAULT_PORT = 8887;
+ public static final int DEFAULT_THREAD_COUNT = 1;
+ private Optional<Server> mServer = Optional.empty();
+
+ @VisibleForTesting
+ Optional<Server> getServer() {
+ return mServer;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void run() {
+ if (!mServer.isPresent()) {
+ mServer =
+ Optional.of(
+ NettyServerBuilder.forAddress(
+ new InetSocketAddress(SERVER_HOSTNAME, DEFAULT_PORT))
+ .addService(this)
+ .executor(Executors.newFixedThreadPool(DEFAULT_THREAD_COUNT))
+ .build());
+ try {
+ mServer.get().start();
+ } catch (IOException e) {
+ LogUtil.CLog.e(e);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void stop() {
+ mServer.ifPresent(Server::shutdown);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setDeviceLister(DeviceLister lister) {
+ // Ignore
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void notifyDeviceStateChange(
+ String serial, DeviceAllocationState oldState, DeviceAllocationState newState) {
+ // Ignore
+ }
+
+ /** The gRPC request handler. */
+ @Override
+ public void getLabResource(
+ LabResourceRequest request, StreamObserver<LabResource> responseObserver) {
+ super.getLabResource(request, responseObserver);
+ }
+}
diff --git a/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java b/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java
index 8833ed5..4a934d8 100644
--- a/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java
+++ b/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java
@@ -24,6 +24,7 @@
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.util.MultiMap;
+import com.android.tradefed.util.ProtoUtil;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
import com.google.protobuf.Descriptors.FieldDescriptor;
@@ -148,48 +149,6 @@
return metrics;
}
- /**
- * Get a nested field reference, i.e. field_1.field_2.field_3, from a proto message as a string.
- * Returns an empty list when a field cannot be found, either because it's invalid or does not
- * exist in the message.
- *
- * <p>If the field reference contains repeated fields, each instance is expanded, resulting in a
- * list of strings.
- */
- private List<String> getNestedFieldFromMessageAsStrings(
- Object messageOrObject, List<String> references) {
- if (references.isEmpty()) {
- return Arrays.asList(String.valueOf(messageOrObject));
- }
- if (!(messageOrObject instanceof Message)) {
- CLog.e(
- "Attempting to read field %s from object of type %s, "
- + "which is not a proto message.",
- references.get(0), messageOrObject.getClass());
- return new ArrayList<String>();
- }
- Message message = (Message) messageOrObject;
- String reference = references.get(0);
- FieldDescriptor fieldDescriptor = message.getDescriptorForType().findFieldByName(reference);
- if (fieldDescriptor == null) {
- CLog.e("Could not find field %s in message %s.", reference, message);
- return new ArrayList<String>();
- }
- Object fieldValue = message.getField(fieldDescriptor);
- if (fieldValue instanceof List) {
- return ((List<? extends Object>) fieldValue)
- .stream()
- .flatMap(
- v ->
- getNestedFieldFromMessageAsStrings(
- v, references.subList(1, references.size()))
- .stream())
- .collect(Collectors.toList());
- }
- return getNestedFieldFromMessageAsStrings(
- fieldValue, references.subList(1, references.size()));
- }
-
/** Fill in the placeholders in the formatter using the proto message as source. */
private List<String> fillInPlaceholders(
String formatter, EventMetricData eventMetric, Message atomContent) {
@@ -202,12 +161,12 @@
List<String> actual = new ArrayList();
if (fieldReference.startsWith("_")) {
actual.addAll(
- getNestedFieldFromMessageAsStrings(
+ ProtoUtil.getNestedFieldFromMessageAsStrings(
eventMetric,
Arrays.asList(fieldReference.substring(1).split("\\."))));
} else {
actual.addAll(
- getNestedFieldFromMessageAsStrings(
+ ProtoUtil.getNestedFieldFromMessageAsStrings(
atomContent, Arrays.asList(fieldReference.split("\\."))));
}
// If both the existing expansion results and newly expanded results have multiple
diff --git a/src/com/android/tradefed/result/CollectingTestListener.java b/src/com/android/tradefed/result/CollectingTestListener.java
index d9ec6ec..a51d2ae 100644
--- a/src/com/android/tradefed/result/CollectingTestListener.java
+++ b/src/com/android/tradefed/result/CollectingTestListener.java
@@ -311,6 +311,12 @@
}
@Override
+ public void testAssumptionFailure(TestDescription test, FailureDescription failure) {
+ setCountDirty();
+ mCurrentTestRunResult.testAssumptionFailure(test, failure);
+ }
+
+ @Override
public void testIgnored(TestDescription test) {
setCountDirty();
mCurrentTestRunResult.testIgnored(test);
diff --git a/src/com/android/tradefed/result/FilteredResultForwarder.java b/src/com/android/tradefed/result/FilteredResultForwarder.java
index 6be2c63..2175fc6 100644
--- a/src/com/android/tradefed/result/FilteredResultForwarder.java
+++ b/src/com/android/tradefed/result/FilteredResultForwarder.java
@@ -22,7 +22,7 @@
import java.util.Map;
/**
- * Variant of {@link ResultForwarder} that only allows a whitelist of {@link TestDescription} to be
+ * Variant of {@link ResultForwarder} that only allows an allowlist of {@link TestDescription} to be
* reported.
*/
public class FilteredResultForwarder extends ResultForwarder {
diff --git a/src/com/android/tradefed/result/JsonHttpTestResultReporter.java b/src/com/android/tradefed/result/JsonHttpTestResultReporter.java
index faa3697..b89e4b0 100644
--- a/src/com/android/tradefed/result/JsonHttpTestResultReporter.java
+++ b/src/com/android/tradefed/result/JsonHttpTestResultReporter.java
@@ -214,8 +214,6 @@
}
allTestMetrics.put(reportingUnit, runResultMetrics);
resultsName.append(String.format("%s%s", reportingUnit, RESULT_SEPARATOR));
- } else {
- CLog.d("Skipping metrics for %s because results are empty.", runResult.getName());
}
// Parse test metrics
@@ -237,8 +235,6 @@
if (testResult.getMetrics().size() > 0) {
JSONObject testResultMetrics = new JSONObject(testResult.getMetrics());
allTestMetrics.put(reportingUnit, testResultMetrics);
- } else {
- CLog.d("Skipping metrics for %s because results are empty.", testDescription);
}
}
}
diff --git a/src/com/android/tradefed/result/LogSaverResultForwarder.java b/src/com/android/tradefed/result/LogSaverResultForwarder.java
index cf11160..0083247 100644
--- a/src/com/android/tradefed/result/LogSaverResultForwarder.java
+++ b/src/com/android/tradefed/result/LogSaverResultForwarder.java
@@ -50,7 +50,12 @@
@Override
public void invocationStarted(IInvocationContext context) {
// Intentionally call invocationStarted for the log saver first.
- mLogSaver.invocationStarted(context);
+ try {
+ mLogSaver.invocationStarted(context);
+ } catch (RuntimeException e) {
+ CLog.e("Caught runtime exception from log saver: %s", mLogSaver.getClass().getName());
+ CLog.e(e);
+ }
InvocationSummaryHelper.reportInvocationStarted(getListeners(), context);
}
diff --git a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
index 6da0c2d..a14cb8a 100644
--- a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
+++ b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
@@ -19,6 +19,8 @@
import com.android.loganalysis.item.LogcatItem;
import com.android.loganalysis.parser.LogcatParser;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.error.DeviceErrorIdentifier;
@@ -41,6 +43,11 @@
/** Special error message from the instrumentation when something goes wrong on device side. */
public static final String ERROR_MESSAGE = "Process crashed.";
public static final String SYSTEM_CRASH_MESSAGE = "System has crashed.";
+ public static final String TIMEOUT_MESSAGES[] = {
+ "Failed to receive adb shell test output",
+ "TimeoutException when running tests",
+ "TestTimedOutException: test timed out after",
+ };
public static final int MAX_NUMBER_CRASH = 3;
@@ -66,16 +73,26 @@
@Override
public void testFailed(TestDescription test, String trace) {
- // If the test case was detected as crashing the instrumentation, we add the crash to it.
- trace = extractCrashAndAddToMessage(trace, mStartTime);
- super.testFailed(test, trace);
+ testFailed(test, FailureDescription.create(trace));
}
@Override
public void testFailed(TestDescription test, FailureDescription failure) {
// If the test case was detected as crashing the instrumentation, we add the crash to it.
String trace = extractCrashAndAddToMessage(failure.getErrorMessage(), mStartTime);
+ if (trace.compareTo(failure.getErrorMessage()) != 0) {
+ // Crash stack trace found, consider this a test failure.
+ failure.setFailureStatus(FailureStatus.TEST_FAILURE);
+ } else if (isTimeout(failure.getErrorMessage())) {
+ failure.setFailureStatus(FailureStatus.TIMED_OUT);
+ }
failure.setErrorMessage(trace);
+ // Add metrics for assessing uncaught IntrumentationTest crash failures (test level).
+ InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.TEST_CRASH_FAILURES, 1);
+ if (FailureStatus.UNSET.equals(failure.getFailureStatus())) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.UNCAUGHT_TEST_CRASH_FAILURES, 1);
+ }
super.testFailed(test, failure);
}
@@ -105,7 +122,13 @@
}
error.setErrorMessage(errorMessage);
if (isCrash(errorMessage)) {
- error.setErrorIdentifier(DeviceErrorIdentifier.INSTRUMENATION_CRASH);
+ error.setErrorIdentifier(DeviceErrorIdentifier.INSTRUMENTATION_CRASH);
+ }
+ // Add metrics for assessing uncaught IntrumentationTest crash failures.
+ InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.CRASH_FAILURES, 1);
+ if (FailureStatus.UNSET.equals(error.getFailureStatus())) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.UNCAUGHT_CRASH_FAILURES, 1);
}
super.testRunFailed(error);
}
@@ -129,6 +152,14 @@
return errorMessage.contains(ERROR_MESSAGE) || errorMessage.contains(SYSTEM_CRASH_MESSAGE);
}
+ private boolean isTimeout(String errorMessage) {
+ for (String timeoutMessage : TIMEOUT_MESSAGES) {
+ if (errorMessage.contains(timeoutMessage)) {
+ return true;
+ }
+ }
+ return false;
+ }
/**
* Extract a formatted object from the logcat snippet.
*
diff --git a/src/com/android/tradefed/result/ResultForwarder.java b/src/com/android/tradefed/result/ResultForwarder.java
index ba38e54..c651405 100644
--- a/src/com/android/tradefed/result/ResultForwarder.java
+++ b/src/com/android/tradefed/result/ResultForwarder.java
@@ -341,6 +341,20 @@
}
@Override
+ public void testAssumptionFailure(TestDescription test, FailureDescription failure) {
+ for (ITestInvocationListener listener : mListeners) {
+ try {
+ listener.testAssumptionFailure(test, failure);
+ } catch (RuntimeException e) {
+ CLog.e(
+ "Exception while invoking %s#testAssumptionFailure",
+ listener.getClass().getName());
+ CLog.e(e);
+ }
+ }
+ }
+
+ @Override
public void testIgnored(TestDescription test) {
for (ITestInvocationListener listener : mListeners) {
try {
diff --git a/src/com/android/tradefed/result/proto/FileProtoResultReporter.java b/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
index 2011c90..3ccbead 100644
--- a/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
@@ -92,6 +92,11 @@
mPeriodicWriting = enabled;
}
+ /** Whether or not periodic writing is enabled. */
+ public boolean isPeriodicWriting() {
+ return mPeriodicWriting;
+ }
+
private void writeProto(TestRecord record) {
if (mOutputFile == null) {
return;
diff --git a/src/com/android/tradefed/result/suite/SuiteResultReporter.java b/src/com/android/tradefed/result/suite/SuiteResultReporter.java
index 424cd9d..82e4685 100644
--- a/src/com/android/tradefed/result/suite/SuiteResultReporter.java
+++ b/src/com/android/tradefed/result/suite/SuiteResultReporter.java
@@ -401,6 +401,9 @@
@Override
public TestSummary getSummary() {
+ if (mSummary == null || mSummary.toString().isEmpty()) {
+ return null;
+ }
TestSummary summary = new TestSummary(new TypedString(mSummary.toString(), Type.TEXT));
summary.setSource(SUITE_REPORTER_SOURCE);
return summary;
diff --git a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
index 772075b..428199d 100644
--- a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
+++ b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
@@ -304,7 +304,7 @@
message = "Run was incomplete. Some tests might not have finished.";
}
serializer.startTag(NS, MODULES_NOT_DONE_REASON);
- serializer.attribute(NS, MESSAGE_ATTR, message);
+ serializer.attribute(NS, MESSAGE_ATTR, sanitizeXmlContent(message));
serializer.endTag(NS, MODULES_NOT_DONE_REASON);
}
serializeTestCases(serializer, module.getTestResults());
diff --git a/src/com/android/tradefed/retry/BaseRetryDecision.java b/src/com/android/tradefed/retry/BaseRetryDecision.java
index 0228ae9..599aed8 100644
--- a/src/com/android/tradefed/retry/BaseRetryDecision.java
+++ b/src/com/android/tradefed/retry/BaseRetryDecision.java
@@ -20,14 +20,20 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.StubDevice;
+import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
+import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.ITestFilterReceiver;
import com.android.tradefed.testtype.retry.IAutoRetriableTest;
+import com.android.tradefed.testtype.suite.ModuleDefinition;
import java.util.ArrayList;
import java.util.HashSet;
@@ -53,6 +59,13 @@
private boolean mRebootAtLastRetry = false;
@Option(
+ name = "reset-at-last-retry",
+ description =
+ "Reset or powerwash the device at the last retry attempt. If this option is "
+ + "set, option `reboot-at-last-retry` will be ignored.")
+ private boolean mResetAtLastRetry = false;
+
+ @Option(
name = "max-testcase-run-count",
description =
"If the IRemoteTest can have its testcases run multiple times, "
@@ -113,6 +126,16 @@
public boolean shouldRetry(
IRemoteTest test, int attemptJustExecuted, List<TestRunResult> previousResults)
throws DeviceNotAvailableException {
+ return shouldRetry(test, null, attemptJustExecuted, previousResults);
+ }
+
+ @Override
+ public boolean shouldRetry(
+ IRemoteTest test,
+ ModuleDefinition module,
+ int attemptJustExecuted,
+ List<TestRunResult> previousResults)
+ throws DeviceNotAvailableException {
// Keep track of some results for the test in progress for statistics purpose.
if (test != mCurrentlyConsideredTest) {
mCurrentlyConsideredTest = test;
@@ -143,7 +166,7 @@
boolean shouldRetry = handleRetryFailures(filterableTest, previousResults);
if (shouldRetry) {
// In case of retry, go through the recovery routine
- recoverStateOfDevices(getDevices(), attemptJustExecuted);
+ recoverStateOfDevices(getDevices(), attemptJustExecuted, module);
}
return shouldRetry;
} else if (test instanceof IAutoRetriableTest) {
@@ -295,13 +318,74 @@
}
/** Recovery attempt on the device to get it a better state before next retry. */
- private void recoverStateOfDevices(List<ITestDevice> devices, int lastAttempt)
+ private void recoverStateOfDevices(
+ List<ITestDevice> devices, int lastAttempt, ModuleDefinition module)
throws DeviceNotAvailableException {
+ if (lastAttempt == (mMaxRetryAttempts - 2)) {
+ if (mResetAtLastRetry) {
+ resetDevice(module, devices);
+ } else if (mRebootAtLastRetry) {
+ for (ITestDevice device : devices) {
+ device.reboot();
+ continue;
+ }
+ }
+ }
+ }
+
+ private void resetDevice(ModuleDefinition module, List<ITestDevice> devices)
+ throws DeviceNotAvailableException {
+ CLog.d("Reset devices...");
+ int deviceResetCount = 0;
for (ITestDevice device : devices) {
- if (mRebootAtLastRetry && (lastAttempt == (mMaxRetryAttempts - 2))) {
- device.reboot();
+ if (!(device instanceof RemoteAndroidVirtualDevice)) {
+ CLog.i(
+ "Device %s of type %s does not support powerwash.",
+ device.getSerialNumber(), device.getClass());
continue;
}
+ boolean success = false;
+ try {
+ success = ((RemoteAndroidVirtualDevice) device).powerwashGce();
+ deviceResetCount++;
+ } catch (TargetSetupError e) {
+ CLog.e(e);
+ throw new DeviceNotAvailableException(
+ String.format(
+ "Failed to powerwash device: %s\nError: %s",
+ device.getSerialNumber(), e.toString()),
+ e,
+ device.getSerialNumber(),
+ DeviceErrorIdentifier.DEVICE_FAILED_TO_RESET);
+ }
+
+ if (!success) {
+ throw new DeviceNotAvailableException(
+ String.format("Failed to powerwash device: %s", device.getSerialNumber()),
+ device.getSerialNumber(),
+ DeviceErrorIdentifier.DEVICE_FAILED_TO_RESET);
+ }
+ }
+
+ if (module != null) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.DEVICE_RESET_MODULES, module.getId());
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.DEVICE_RESET_COUNT, deviceResetCount);
+
+ // Run all preparers including suite level ones.
+ Throwable preparationException =
+ module.runPreparation(true /* includeSuitePreparers */);
+ if (preparationException != null) {
+ CLog.e(preparationException);
+ throw new DeviceNotAvailableException(
+ String.format(
+ "Failed to reset devices before retry: %s",
+ preparationException.toString()),
+ preparationException,
+ devices.get(0).getSerialNumber(),
+ DeviceErrorIdentifier.DEVICE_FAILED_TO_RESET);
+ }
}
}
}
diff --git a/src/com/android/tradefed/retry/IRetryDecision.java b/src/com/android/tradefed/retry/IRetryDecision.java
index 70b3e00..bc0face 100644
--- a/src/com/android/tradefed/retry/IRetryDecision.java
+++ b/src/com/android/tradefed/retry/IRetryDecision.java
@@ -19,6 +19,7 @@
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.result.TestRunResult;
import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.suite.ModuleDefinition;
import java.util.List;
@@ -58,6 +59,24 @@
throws DeviceNotAvailableException;
/**
+ * Decide whether or not retry should be attempted. Also make any necessary changes to the
+ * {@link IRemoteTest} to be retried (Applying filters, etc.).
+ *
+ * @param test The {@link IRemoteTest} that just ran.
+ * @param module The {@link ModuleDefinition} object for the test module.
+ * @param attemptJustExecuted The number of the attempt that we just ran.
+ * @param previousResults The list of {@link TestRunResult} of the test that just ran.
+ * @return True if we should retry, False otherwise.
+ * @throws DeviceNotAvailableException Can be thrown during device recovery
+ */
+ public boolean shouldRetry(
+ IRemoteTest test,
+ ModuleDefinition module,
+ int attemptJustExecuted,
+ List<TestRunResult> previousResults)
+ throws DeviceNotAvailableException;
+
+ /**
* {@link #shouldRetry(IRemoteTest, int, List)} will most likely be called before the last retry
* attempt, so we might be missing the very last attempt results for statistics purpose. This
* method allows those results to be provided for proper statistics calculations.
diff --git a/src/com/android/tradefed/retry/ResultAggregator.java b/src/com/android/tradefed/retry/ResultAggregator.java
index 43848fd..b028bdf 100644
--- a/src/com/android/tradefed/retry/ResultAggregator.java
+++ b/src/com/android/tradefed/retry/ResultAggregator.java
@@ -257,6 +257,12 @@
}
@Override
+ public void testAssumptionFailure(TestDescription test, FailureDescription failure) {
+ super.testAssumptionFailure(test, failure);
+ mDetailedForwarder.testAssumptionFailure(test, failure);
+ }
+
+ @Override
public void testFailed(TestDescription test, String trace) {
super.testFailed(test, trace);
mDetailedForwarder.testFailed(test, trace);
diff --git a/src/com/android/tradefed/sandbox/ISandbox.java b/src/com/android/tradefed/sandbox/ISandbox.java
index c78c1a9..ba0fc67 100644
--- a/src/com/android/tradefed/sandbox/ISandbox.java
+++ b/src/com/android/tradefed/sandbox/ISandbox.java
@@ -65,7 +65,7 @@
*/
public File getTradefedSandboxEnvironment(
IInvocationContext context, IConfiguration nonVersionedConfig, String[] args)
- throws ConfigurationException;
+ throws Exception;
/**
* Create a classpath based on the environment and the working directory returned by {@link
diff --git a/src/com/android/tradefed/sandbox/TradefedSandbox.java b/src/com/android/tradefed/sandbox/TradefedSandbox.java
index 71b13a4..942955d 100644
--- a/src/com/android/tradefed/sandbox/TradefedSandbox.java
+++ b/src/com/android/tradefed/sandbox/TradefedSandbox.java
@@ -125,6 +125,7 @@
// Allow interruption, subprocess should handle signals itself
mRunUtil.allowInterrupt(true);
CommandResult result = null;
+ RuntimeException interruptedException = null;
try {
result =
mRunUtil.runTimedCmd(
@@ -132,6 +133,7 @@
} catch (RuntimeException interrupted) {
CLog.e("Sandbox runtimedCmd threw an exception");
CLog.e(interrupted);
+ interruptedException = interrupted;
result = new CommandResult(CommandStatus.EXCEPTION);
result.setStdout(StreamUtil.getStackTrace(interrupted));
}
@@ -156,7 +158,9 @@
} else {
joinResult = mEventParser.joinReceiver(waitTime);
}
-
+ if (interruptedException != null) {
+ throw interruptedException;
+ }
if (!joinResult) {
if (!failedStatus) {
result.setStatus(CommandStatus.EXCEPTION);
@@ -164,15 +168,15 @@
result.setStderr(
String.format("Event receiver thread did not complete.:\n%s", stderrText));
}
- if (mProtoReceiver != null) {
- mProtoReceiver.completeModuleEvents();
- }
PrettyPrintDelimiter.printStageDelimiter(
String.format(
"Execution of the tests occurred in the sandbox, you can find its logs "
+ "under the name pattern '%s*'",
SANDBOX_PREFIX));
} finally {
+ if (mProtoReceiver != null) {
+ mProtoReceiver.completeModuleEvents();
+ }
// Log the configuration used to run
try (InputStreamSource configFile =
new FileInputStreamSource(mSerializedConfiguration)) {
@@ -252,7 +256,7 @@
config.getCommandLine(),
/** no logging */
false));
- } catch (ConfigurationException e) {
+ } catch (Exception e) {
return e;
}
@@ -289,7 +293,7 @@
@Override
public File getTradefedSandboxEnvironment(
IInvocationContext context, IConfiguration nonVersionedConfig, String[] args)
- throws ConfigurationException {
+ throws Exception {
SandboxOptions options = getSandboxOptions(nonVersionedConfig);
// Check that we have no args conflicts.
if (options.getSandboxTfDirectory() != null && options.getSandboxBuildId() != null) {
diff --git a/src/com/android/tradefed/targetprep/DeviceFailedToBootError.java b/src/com/android/tradefed/targetprep/DeviceFailedToBootError.java
index 45cfbc6..5e741a2 100644
--- a/src/com/android/tradefed/targetprep/DeviceFailedToBootError.java
+++ b/src/com/android/tradefed/targetprep/DeviceFailedToBootError.java
@@ -21,15 +21,19 @@
/**
* Thrown if a device fails to boot after being flashed with a build.
*/
-@SuppressWarnings("serial")
public class DeviceFailedToBootError extends BuildError {
+ private static final long serialVersionUID = -6539557027017640715L;
+
/**
* Constructs a new (@link DeviceFailedToBootError} with a detailed error message.
*
* @param reason an error message giving more details about the boot failure
* @param descriptor the descriptor of the device concerned by the exception
+ * @deprecated Use {@link #DeviceFailedToBootError(String, DeviceDescriptor, ErrorIdentifier)}
+ * instead
*/
+ @Deprecated
public DeviceFailedToBootError(String reason, DeviceDescriptor descriptor) {
super(reason, descriptor);
}
diff --git a/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java b/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java
index 350c1e9..00f7913 100644
--- a/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java
+++ b/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java
@@ -264,10 +264,12 @@
device.waitForDeviceAvailable(mDeviceBootTime);
} catch (DeviceUnresponsiveException e) {
// assume this is a build problem
- throw new DeviceFailedToBootError(String.format(
- "Device %s did not become available after flashing %s",
- device.getSerialNumber(), deviceBuild.getDeviceBuildId()),
- device.getDeviceDescriptor());
+ throw new DeviceFailedToBootError(
+ String.format(
+ "Device %s did not become available after flashing %s",
+ device.getSerialNumber(), deviceBuild.getDeviceBuildId()),
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.ERROR_AFTER_FLASHING);
}
device.postBootSetup();
} finally {
diff --git a/src/com/android/tradefed/targetprep/DeviceSetup.java b/src/com/android/tradefed/targetprep/DeviceSetup.java
index 2db82ba..539ecff 100644
--- a/src/com/android/tradefed/targetprep/DeviceSetup.java
+++ b/src/com/android/tradefed/targetprep/DeviceSetup.java
@@ -746,6 +746,21 @@
device.executeShellCommand("chmod 644 /data/local.prop");
CLog.i("Rebooting %s due to system property change", device.getSerialNumber());
device.reboot();
+
+ // Verify properties have expected values.
+ List<String> unmatched = new ArrayList<>();
+ for (Map.Entry<String, String> prop : mSetProps.entrySet()) {
+ String actual = device.getProperty(prop.getKey());
+ String expected = prop.getValue();
+ if (!actual.equals(expected)) {
+ unmatched.add(String.format("%s=%s(expected:%s)", prop.getKey(), actual, expected));
+ }
+ }
+ if (unmatched.size() > 0) {
+ throw new TargetSetupError(
+ String.format("Failed to set properties: %s", unmatched),
+ device.getDeviceDescriptor());
+ }
}
/**
@@ -894,7 +909,7 @@
CLog.d("Skipping connect wifi due to force-skip-run-commands");
return;
}
- if (mWifiSsid == null && mWifiSsidToPsk.isEmpty()) {
+ if ((mWifiSsid == null || mWifiSsid.isEmpty()) && mWifiSsidToPsk.isEmpty()) {
return;
}
diff --git a/src/com/android/tradefed/targetprep/DeviceUpdateTargetPreparer.java b/src/com/android/tradefed/targetprep/DeviceUpdateTargetPreparer.java
index e6ff4cc..3218365 100644
--- a/src/com/android/tradefed/targetprep/DeviceUpdateTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/DeviceUpdateTargetPreparer.java
@@ -25,6 +25,7 @@
import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.result.error.InfraErrorIdentifier;
import java.io.File;
@@ -66,7 +67,7 @@
throw new TargetSetupError(
"Device image file not found: " + deviceUpdateImage.getAbsolutePath(),
device.getDeviceDescriptor(),
- InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
preUpdateActions(deviceUpdateImage, device);
// flashing concurrency control
@@ -106,7 +107,8 @@
String.format(
"Device %s did not become available after flashing %s",
device.getSerialNumber(), deviceUpdateImage.getAbsolutePath()),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.ERROR_AFTER_FLASHING);
}
CLog.i("Device update completed on %s", device.getDeviceDescriptor());
// calling this last because we want to inject device side build info after device boots up
diff --git a/src/com/android/tradefed/targetprep/FlashingResourcesParser.java b/src/com/android/tradefed/targetprep/FlashingResourcesParser.java
index 87d6ee6..51af0d9 100644
--- a/src/com/android/tradefed/targetprep/FlashingResourcesParser.java
+++ b/src/com/android/tradefed/targetprep/FlashingResourcesParser.java
@@ -277,7 +277,7 @@
"Could not find %s in device image zip %s",
ANDROID_INFO_FILE_NAME, deviceImgZipFile.getName()),
nullDescriptor,
- InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
infoReader = new BufferedReader(new InputStreamReader(
deviceZip.getInputStream(androidInfoEntry)));
diff --git a/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java b/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java
index 72a89d9..d896f00 100644
--- a/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java
+++ b/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java
@@ -15,7 +15,7 @@
*/
package com.android.tradefed.targetprep;
-import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
@@ -26,6 +26,7 @@
import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
@@ -79,7 +80,7 @@
public void setUp(TestInformation testInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
ITestDevice device = testInfo.getDevice();
- IDeviceBuildInfo buildInfo = (IDeviceBuildInfo) testInfo.getBuildInfo();
+ IBuildInfo buildInfo = testInfo.getBuildInfo();
File tmpDir = null;
try {
@@ -106,7 +107,8 @@
String.format(
"Device %s did not become available after flashing GKI. Exception: %s",
device.getSerialNumber(), e),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.ERROR_AFTER_FLASHING);
}
device.postBootSetup();
CLog.i("Device update completed on %s", device.getDeviceDescriptor());
@@ -136,10 +138,10 @@
* Flash GKI images.
*
* @param device the {@link ITestDevice}
- * @param buildInfo the {@link IDeviceBuildInfo} the device build info
+ * @param buildInfo the {@link IBuildInfo} the build info
* @throws TargetSetupError, DeviceNotAvailableException, IOException
*/
- private void flashGki(ITestDevice device, IDeviceBuildInfo buildInfo)
+ private void flashGki(ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError, DeviceNotAvailableException {
IDeviceManager deviceManager = getDeviceManager();
device.waitForDeviceOnline();
@@ -178,13 +180,13 @@
* Validate GKI boot image is expected. (Obsoleted. Please call with tmpDir provided)
*
* @param device the {@link ITestDevice}
- * @param buildInfo the {@link IDeviceBuildInfo} the device build info
+ * @param buildInfo the {@link IBuildInfo} the build info
* @throws TargetSetupError if there is no valid gki boot.img
*/
- public void validateGkiBootImg(ITestDevice device, IDeviceBuildInfo buildInfo)
+ public void validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError {
throw new TargetSetupError(
- "Obsoleted. Please use validateGkiBootImg(ITestDevice, IDeviceBuildInfo, File)",
+ "Obsoleted. Please use validateGkiBootImg(ITestDevice, IBuildInfo, File)",
device.getDeviceDescriptor());
}
@@ -192,12 +194,12 @@
* Validate GKI boot image is expected. Throw exception if there is no valid boot.img.
*
* @param device the {@link ITestDevice}
- * @param buildInfo the {@link IDeviceBuildInfo} the device build info
+ * @param buildInfo the {@link IBuildInfo} the build info
* @param tmpDir the temporary directory {@link File}
* @throws TargetSetupError if there is no valid gki boot.img
*/
@VisibleForTesting
- protected void validateGkiBootImg(ITestDevice device, IDeviceBuildInfo buildInfo, File tmpDir)
+ protected void validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir)
throws TargetSetupError {
if (buildInfo.getFile(GKI_BOOT_IMG) != null && mBootImageFileName != null) {
mBootImg =
@@ -246,40 +248,6 @@
}
/**
- * Flash device images.
- *
- * @param device the {@link ITestDevice}
- * @param buildInfo the {@link IDeviceBuildInfo} the device build info
- * @throws TargetSetupError, DeviceNotAvailableException
- */
- private void flashDeviceImage(ITestDevice device, IDeviceBuildInfo buildInfo)
- throws TargetSetupError, DeviceNotAvailableException {
- IDeviceManager deviceManager = getDeviceManager();
- long start = System.currentTimeMillis();
- deviceManager.takeFlashingPermit();
- CLog.v(
- "Flashing permit obtained after %ds",
- TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start)));
- // don't allow interruptions during flashing operations.
- getRunUtil().allowInterrupt(false);
- try {
- executeFastbootCmd(
- device,
- "--skip-reboot",
- "--disable-verity",
- "update",
- buildInfo.getDeviceImageFile().getAbsolutePath());
- } finally {
- // Allow interruption at the end no matter what.
- getRunUtil().allowInterrupt(true);
- deviceManager.returnFlashingPermit();
- CLog.v(
- "Flashing permit returned after %ds",
- TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start)));
- }
- }
-
- /**
* Helper method to execute host command.
*
* @param device the {@link ITestDevice}
@@ -338,7 +306,10 @@
ZipUtil2.extractZip(sourceFile, destDir);
requestedFile = FileUtil.findFile(destDir, requestedFileName);
} catch (IOException e) {
- throw new TargetSetupError(e.getMessage(), e, device.getDeviceDescriptor());
+ throw new TargetSetupError(
+ String.format("Fail to get %s from %s", requestedFileName, sourceFile),
+ e,
+ device.getDeviceDescriptor());
}
} else if (sourceFile.isDirectory()) {
requestedFile = FileUtil.findFile(sourceFile, requestedFileName);
@@ -381,7 +352,7 @@
throw new TargetSetupError(
String.format(
"fastboot command %s failed in device %s. stdout: %s, stderr: %s",
- cmdArgs[0],
+ cmdArgs,
device.getSerialNumber(),
result.getStdout(),
result.getStderr()),
diff --git a/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java b/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java
index 23ce54d..25bb99f 100644
--- a/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java
+++ b/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java
@@ -15,7 +15,7 @@
*/
package com.android.tradefed.targetprep;
-import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
@@ -26,6 +26,7 @@
import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
@@ -48,9 +49,6 @@
@OptionClass(alias = "gsi-device-flash-preparer")
public class GsiDeviceFlashPreparer extends BaseTargetPreparer {
- private static final String GSI_SYSTEM_IMG = "gsi_system.img";
- private static final String GSI_VBMETA_IMG = "gsi_vbmeta.img";
- private static final String GKI_BOOT_IMG = "gki_boot.img";
private static final int DYNAMIC_PARTITION_API_LEVEL = 29;
// Wait time for device state to stablize in millisecond
private static final int STATE_STABLIZATION_WAIT_TIME_MLLISECS = 60000;
@@ -62,26 +60,40 @@
private long mDeviceBootTime = 5 * 60 * 1000;
@Option(
+ name = "system-image-zip-name",
+ description = "The name of the zip file containing the system image in BuildInfo.")
+ private String mSystemImageZipName = "gsi_system.img";
+
+ @Option(
name = "system-image-file-name",
description =
- "The system image file name to search for if provided gsi_system.img is in "
- + "a zip file or directory.")
+ "The system image file name to search for if provided system image "
+ + "is in a zip file or directory.")
private String mSystemImageFileName = "system.img";
@Option(
+ name = "vbmeta-image-zip-name",
+ description = "The name of the zip file containing the system image in BuildInfo.")
+ private String mVbmetaImageZipName = "gsi_vbmeta.img";
+
+ @Option(
name = "vbmeta-image-file-name",
description =
- "The vbmeta image file name to search for if provided gsi_vbmeta.img is in "
- + "a zip file or directory.")
+ "The vbmeta image file name to search for if provided vbmeta image is "
+ + "in a zip file or directory.")
private String mVbmetaImageFileName = "vbmeta.img";
@Option(
+ name = "boot-image-zip-name",
+ description = "The name of the zip file containing the boot image in BuildInfo.")
+ private String mBootImageZipName = "gki_boot.img";
+
+ @Option(
name = "boot-image-file-name",
description =
- "The boot image file name to search for if gki_boot.img is provided in BuildInfo and the provided "
- + "file is a zip file or directory, for example boot-5.4.img. By default when gki_boot.img "
- + "is provided in BuildInfo with a zip file or file directory, the target preparer will use"
- + " the first found file that matches boot(.*).img as file name.")
+ "The boot image file name to search for if boot image is is in a zip "
+ + "file or directory, for example boot-5.4.img. The first file"
+ + "match the provided name string will be used.")
private String mBootImageFileName = "boot(.*).img";
@Option(
@@ -98,11 +110,11 @@
public void setUp(TestInformation testInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
ITestDevice device = testInfo.getDevice();
- IDeviceBuildInfo buildInfo = (IDeviceBuildInfo) testInfo.getBuildInfo();
+ IBuildInfo buildInfo = testInfo.getBuildInfo();
File tmpDir = null;
try {
- tmpDir = FileUtil.createTempDir("gki_preparer");
+ tmpDir = FileUtil.createTempDir("gsi_preparer");
validateGsiImg(device, buildInfo, tmpDir);
flashGsi(device, buildInfo);
} catch (IOException ioe) {
@@ -125,7 +137,8 @@
String.format(
"Device %s did not become available after flashing GKI. Exception: %s",
device.getSerialNumber(), e),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.ERROR_AFTER_FLASHING);
}
device.postBootSetup();
CLog.i("Device update completed on %s", device.getDeviceDescriptor());
@@ -155,10 +168,10 @@
* Flash GSI images.
*
* @param device the {@link ITestDevice}
- * @param buildInfo the {@link IDeviceBuildInfo} the device build info
+ * @param buildInfo the {@link IBuildInfo} the build info
* @throws TargetSetupError, DeviceNotAvailableException, IOException
*/
- private void flashGsi(ITestDevice device, IDeviceBuildInfo buildInfo)
+ private void flashGsi(ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError, DeviceNotAvailableException {
IDeviceManager deviceManager = getDeviceManager();
device.waitForDeviceOnline();
@@ -196,11 +209,10 @@
if (shouldUseFastbootd) {
device.rebootIntoFastbootd();
if (mShouldEraseProductPartition) {
- executeFastbootCmd(
- device, "delete-logical-partition", "product" + currSlot);
+ device.executeLongFastbootCommand(
+ "delete-logical-partition", "product" + currSlot);
}
}
- String systemPartition = "system";
executeFastbootCmd(device, "erase", "system" + currSlot);
executeFastbootCmd(device, "flash", "system", mSystemImg.getAbsolutePath());
executeFastbootCmd(device, "-w");
@@ -223,33 +235,38 @@
* Validate GSI image is expected. Throw exception if there is no valid GSI image.
*
* @param device the {@link ITestDevice}
- * @param buildInfo the {@link IDeviceBuildInfo} the device build info
+ * @param buildInfo the {@link IBuildInfo} the build info
* @param tmpDir the temporary directory {@link File}
* @throws TargetSetupError if there is no valid gki boot.img
*/
- private void validateGsiImg(ITestDevice device, IDeviceBuildInfo buildInfo, File tmpDir)
+ private void validateGsiImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir)
throws TargetSetupError {
- if (buildInfo.getFile(GSI_SYSTEM_IMG) == null) {
+ if (buildInfo.getFile(mSystemImageZipName) == null) {
throw new TargetSetupError(
- String.format("BuildInfo doesn't contain file key %s.", GSI_SYSTEM_IMG),
- device.getDeviceDescriptor());
- }
- if (buildInfo.getFile(GSI_VBMETA_IMG) == null) {
- throw new TargetSetupError(
- String.format("BuildInfo doesn't contain file key %s.", GSI_VBMETA_IMG),
+ String.format("BuildInfo doesn't contain file key %s.", mSystemImageZipName),
device.getDeviceDescriptor());
}
mSystemImg =
getRequestedFile(
- device, mSystemImageFileName, buildInfo.getFile(GSI_SYSTEM_IMG), tmpDir);
- mVbmetaImg =
- getRequestedFile(
- device, mVbmetaImageFileName, buildInfo.getFile(GSI_VBMETA_IMG), tmpDir);
-
- if (buildInfo.getFile(GKI_BOOT_IMG) != null && mBootImageFileName != null) {
+ device,
+ mSystemImageFileName,
+ buildInfo.getFile(mSystemImageZipName),
+ tmpDir);
+ if (buildInfo.getFile(mVbmetaImageZipName) != null) {
+ mVbmetaImg =
+ getRequestedFile(
+ device,
+ mVbmetaImageFileName,
+ buildInfo.getFile(mVbmetaImageZipName),
+ tmpDir);
+ }
+ if (buildInfo.getFile(mBootImageZipName) != null && mBootImageFileName != null) {
mBootImg =
getRequestedFile(
- device, mBootImageFileName, buildInfo.getFile(GKI_BOOT_IMG), tmpDir);
+ device,
+ mBootImageFileName,
+ buildInfo.getFile(mBootImageZipName),
+ tmpDir);
}
}
@@ -299,7 +316,10 @@
ZipUtil2.extractZip(sourceFile, destDir);
requestedFile = FileUtil.findFile(destDir, requestedFileName);
} catch (IOException e) {
- throw new TargetSetupError(e.getMessage(), e, device.getDeviceDescriptor());
+ throw new TargetSetupError(
+ String.format("Fail to get %s from %s", requestedFileName, sourceFile),
+ e,
+ device.getDeviceDescriptor());
}
} else if (sourceFile.isDirectory()) {
requestedFile = FileUtil.findFile(sourceFile, requestedFileName);
@@ -342,7 +362,7 @@
throw new TargetSetupError(
String.format(
"fastboot command %s failed in device %s. stdout: %s, stderr: %s",
- cmdArgs[0],
+ cmdArgs,
device.getSerialNumber(),
result.getStdout(),
result.getStderr()),
diff --git a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
index d6db235..936df0f 100644
--- a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
@@ -65,15 +65,20 @@
private static final int R_SDK_INT = 30;
private List<ApexInfo> mTestApexInfoList = new ArrayList<>();
+ private List<ApexInfo> mModulesToUninstall = new ArrayList<>();
private Set<String> mApkToInstall = new LinkedHashSet<>();
private List<String> mApkInstalled = new ArrayList<>();
private List<String> mSplitsInstallArgs = new ArrayList<>();
private BundletoolUtil mBundletoolUtil;
private String mDeviceSpecFilePath = "";
+ private boolean mOptimizeMainlineTest = false;
@Option(name = "bundletool-file-name", description = "The file name of the bundletool jar.")
private String mBundletoolFilename;
+ @Option(name = "train-path", description = "The absoulte path of the train folder.")
+ private File mTrainFolderPath;
+
@Option(
name = "apex-staging-wait-time",
description = "The time in ms to wait for apex staged session ready.",
@@ -88,18 +93,36 @@
+ "preloaded on device. Otherwise an exception will be thrown.")
private boolean mIgnoreIfNotPreloaded = false;
+ @Option(
+ name = "skip-apex-teardown",
+ description = "Skip teardown if all files to be installed are apex files. "
+ + "Currently, this option is only used for Test Mapping use case.")
+ private boolean mSkipApexTearDown = false;
+
@Override
public void setUp(TestInformation testInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
setTestInformation(testInfo);
ITestDevice device = testInfo.getDevice();
- if (getTestsFileName().isEmpty()) {
+ if (mTrainFolderPath != null) {
+ addApksToTestFiles();
+ }
+
+ List<File> moduleFileNames = getTestsFileName();
+ if (moduleFileNames.isEmpty()) {
CLog.i("No apk/apex module file to install. Skipping.");
return;
}
- cleanUpStagedAndActiveSession(device);
+ if (!mSkipApexTearDown || hasApkFilesToInstall(moduleFileNames)) {
+ // Cleanup the device if skip-apex-teardown isn't set or not all files to be installed
+ // are apex files. It will always run with the target preparer.
+ cleanUpStagedAndActiveSession(device);
+ }
+ else {
+ mOptimizeMainlineTest = true;
+ }
Set<ApexInfo> activatedApexes = device.getActiveApexes();
@@ -113,9 +136,36 @@
CLog.i("No modules are preloaded on the device, so no modules will be installed.");
return;
}
+
+ if (mOptimizeMainlineTest) {
+ CLog.i("Optimizing install apex module target preparer.");
+ // Get the apex files that are already installed on the device.
+ Set<ApexInfo> apexInData = getApexInData(activatedApexes);
+
+ // Get the apex files that are not used by the current test and will be uninstalled.
+ mModulesToUninstall.addAll(
+ getModulesToUninstall(apexInData, testAppFiles, device));
+
+ for (ApexInfo m : mModulesToUninstall) {
+ CLog.i("Uninstalling module: %s", m.name);
+ super.uninstallPackage(device, m.name);
+ }
+
+ if (testAppFiles.isEmpty()) {
+ if (!mModulesToUninstall.isEmpty()) {
+ RunUtil.getDefault().sleep(mApexStagingWaitTime);
+ device.reboot();
+ }
+ // If both the list of files to be installed and uninstalled are empty, that means
+ // the mainline modules are the same as the previous ones.
+ CLog.i("All required modules are installed");
+ return;
+ }
+ }
+
if (containsApks(testAppFiles)) {
installUsingBundleTool(testInfo, testAppFiles);
- if (mTestApexInfoList.isEmpty()) {
+ if (mTestApexInfoList.isEmpty() && mModulesToUninstall.isEmpty()) {
CLog.i("No Apex module in the train. Skipping reboot.");
return;
} else {
@@ -158,13 +208,75 @@
String.format(
"Failed to activate %s on device %s.",
listApexInfo(failToActivateApex).toString(), device.getSerialNumber()),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.FAIL_ACTIVATE_APEX);
}
CLog.i("Train activation succeed.");
}
+ /**
+ * Get a set of modules that will be uninstalled.
+ *
+ * @param apexInData A Set<ApexInfo> of modules that are installed on the /data directory.
+ * @param testFiles A List<File> of modules that will be installed on the device.
+ * @param device the {@link ITestDevice}
+ * @return A Set<ApexInfo> of modules that will be uninstalled on the device.
+ */
+ @VisibleForTesting
+ Set<ApexInfo> getModulesToUninstall(Set<ApexInfo> apexInData,
+ List<File> testFiles, ITestDevice device) throws TargetSetupError {
+ Set<ApexInfo> unInstallModules = new HashSet<>(apexInData);
+ List<File> filesToSkipInstall = new ArrayList<>();
+ for (File testFile : testFiles) {
+ String packageName = parsePackageName(testFile, device.getDeviceDescriptor());
+ for (ApexInfo apexModule : apexInData) {
+ if (apexModule.name.equals(packageName)) {
+ unInstallModules.remove(apexModule);
+ filesToSkipInstall.add(testFile);
+ }
+ }
+ }
+ // Update the modules to be installed based on what will not be installed.
+ testFiles.removeAll(filesToSkipInstall);
+ return unInstallModules;
+ }
+
+ /**
+ * Return a set of files that is already installed on the /data directory.
+ */
+ @VisibleForTesting
+ Set<ApexInfo> getApexInData(Set<ApexInfo> activatedApexes) {
+ Set<ApexInfo> apexInData = new HashSet<>();
+ for (ApexInfo apex : activatedApexes) {
+ if (apex.sourceDir.startsWith(ACTIVATED_APEX_SOURCEDIR_PREFIX, 1)) {
+ apexInData.add(apex);
+ }
+ }
+ return apexInData;
+ }
+
+ /**
+ * Check if the files to be installed contain .apk or .apks.
+ *
+ * @param testAppFiles List<File> of the modules that will be installed on the device.
+ * @return true if the files contain .apk or .apks, otherwise false.
+ */
+ private boolean hasApkFilesToInstall(List<File> testAppFiles) {
+ List<String> checkLists = Arrays.asList(".apk", ".apks");
+ for (File testAppFile : testAppFiles) {
+ if (checkLists.stream().anyMatch(entry -> testAppFile.getName().endsWith(entry))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ if (mOptimizeMainlineTest) {
+ CLog.d("Skipping tearDown since the installed modules may be used for the next test.");
+ return;
+ }
ITestDevice device = testInfo.getDevice();
if (e instanceof DeviceNotAvailableException) {
CLog.e("Device %s is not available. Teardown() skipped.", device.getSerialNumber());
@@ -193,12 +305,18 @@
if (mBundletoolUtil != null) {
return;
}
- File bundletoolJar = getLocalPathForFilename(testInfo, getBundletoolFileName());
+ File bundletoolJar;
+ File f = new File(getBundletoolFileName());
+ if (!f.isAbsolute()) {
+ bundletoolJar = getLocalPathForFilename(testInfo, getBundletoolFileName());
+ } else {
+ bundletoolJar = f;
+ }
if (bundletoolJar == null) {
throw new TargetSetupError(
String.format("Failed to find bundletool jar %s.", getBundletoolFileName()),
testInfo.getDevice().getDeviceDescriptor(),
- InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
mBundletoolUtil = new BundletoolUtil(bundletoolJar);
}
@@ -272,7 +390,10 @@
List<File> moduleNamesToInstall = new ArrayList<>();
for (File moduleFileName : moduleFileNames) {
// getLocalPathForFilename throws if apk not found
- File moduleFile = getLocalPathForFilename(testInfo, moduleFileName.getName());
+ File moduleFile = moduleFileName;
+ if (!moduleFile.isAbsolute()) {
+ moduleFile = getLocalPathForFilename(testInfo, moduleFileName.getName());
+ }
String modulePackageName = "";
if (moduleFile.getName().endsWith(SPLIT_APKS_SUFFIX)) {
List<File> splits = getSplitsForApks(testInfo, moduleFile);
@@ -470,7 +591,12 @@
throws TargetSetupError, DeviceNotAvailableException {
ITestDevice device = testInfo.getDevice();
for (File moduleFileName : testAppFileNames) {
- File moduleFile = getLocalPathForFilename(testInfo, moduleFileName.getName());
+ File moduleFile;
+ if (!moduleFileName.isAbsolute()) {
+ moduleFile = getLocalPathForFilename(testInfo, moduleFileName.getName());
+ } else {
+ moduleFile = moduleFileName;
+ }
if (moduleFileName.getName().endsWith(SPLIT_APKS_SUFFIX)) {
List<File> splits = getSplitsForApks(testInfo, moduleFile);
String splitsArgs = createInstallArgsForSplit(splits, device);
@@ -727,6 +853,16 @@
return failToActivateApex;
}
+ private void addApksToTestFiles() {
+ File[] filesUnderTrainFolder = mTrainFolderPath.listFiles();
+ Arrays.sort(filesUnderTrainFolder, (a, b) -> a.getName().compareTo(b.getName()));
+ for (File f : filesUnderTrainFolder) {
+ if (f.getName().endsWith(".apks")) {
+ getTestsFileName().add(f);
+ }
+ }
+ }
+
@VisibleForTesting
protected String getBundletoolFileName() {
return mBundletoolFilename;
@@ -741,4 +877,9 @@
protected List<String> getApkInstalled() {
return mApkInstalled;
}
+
+ @VisibleForTesting
+ public void setSkipApexTearDown(boolean skip) {
+ mSkipApexTearDown = skip;
+ }
}
diff --git a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
index fce3d8d..0490685 100644
--- a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
+++ b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
@@ -33,6 +33,7 @@
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
import com.android.tradefed.util.AaptParser;
+import com.android.tradefed.util.AaptParser.AaptVersion;
import com.android.tradefed.util.AbiFormatter;
import com.android.tradefed.util.BuildTestsZipUtils;
@@ -152,6 +153,9 @@
@Option(name = "instant-mode", description = "Whether or not to install apk in instant mode.")
private boolean mInstantMode = false;
+ @Option(name = "aapt-version", description = "The version of AAPT for APK parsing.")
+ private AaptVersion mAaptVersion = AaptVersion.AAPT;
+
@Option(
name = "force-install-mode",
description =
@@ -163,7 +167,7 @@
private Integer mUserId = null;
private Boolean mGrantPermission = null;
- private Set<String> mPackagesInstalled = null;
+ private Set<String> mPackagesInstalled = new HashSet<>();
private TestInformation mTestInfo;
protected void setTestInformation(TestInformation testInfo) {
@@ -221,6 +225,11 @@
mGrantPermission = shouldGrant;
}
+ /** Sets the version of AAPT for APK parsing. */
+ public void setAaptVersion(AaptVersion aaptVersion) {
+ mAaptVersion = aaptVersion;
+ }
+
/** Adds one apk installation arg to be used. */
public void addInstallArg(String arg) {
mInstallArgs.add(arg);
@@ -251,7 +260,7 @@
apkFileName, testInfo.getBuildInfo().toString()),
ioe,
testInfo.getDevice().getDeviceDescriptor(),
- InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
}
@@ -277,9 +286,6 @@
CLog.i("No test apps to install, skipping");
return;
}
- if (mCleanup) {
- mPackagesInstalled = new HashSet<>();
- }
// resolve abi flags
if (mAbi != null && mForceAbi != null) {
@@ -370,7 +376,7 @@
@Override
public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
mTestInfo = testInfo;
- if (mCleanup && mPackagesInstalled != null && !(e instanceof DeviceNotAvailableException)) {
+ if (mCleanup && !(e instanceof DeviceNotAvailableException)) {
for (String packageName : mPackagesInstalled) {
try {
uninstallPackage(getDevice(), packageName);
@@ -471,7 +477,7 @@
throw new TargetSetupError(
String.format("Test app %s was not found.", apkFile.getName()),
device.getDeviceDescriptor(),
- InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
} else {
CLog.d("Test app %s was not found.", apkFile.getName());
continue;
@@ -517,14 +523,16 @@
String.format(
"Could not list files of specified directory: %s", fileOrDirectory),
e,
- deviceDescriptor);
+ deviceDescriptor,
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
if (mThrowIfNoFile && apkFiles.isEmpty()) {
throw new TargetSetupError(
String.format(
"Could not find any files in specified directory: %s", fileOrDirectory),
- deviceDescriptor);
+ deviceDescriptor,
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
return apkFiles;
@@ -588,7 +596,7 @@
/** Get the package name from the test app. */
protected String parsePackageName(File testAppFile, DeviceDescriptor deviceDescriptor)
throws TargetSetupError {
- AaptParser parser = AaptParser.parse(testAppFile);
+ AaptParser parser = AaptParser.parse(testAppFile, mAaptVersion);
if (parser == null) {
throw new TargetSetupError(
"apk installed but AaptParser failed",
@@ -598,4 +606,3 @@
return parser.getPackageName();
}
}
-
diff --git a/src/com/android/tradefed/testtype/NoisyDryRunTest.java b/src/com/android/tradefed/testtype/NoisyDryRunTest.java
index 3e17cdf..9908ee7 100644
--- a/src/com/android/tradefed/testtype/NoisyDryRunTest.java
+++ b/src/com/android/tradefed/testtype/NoisyDryRunTest.java
@@ -20,11 +20,13 @@
import com.android.tradefed.command.CommandFileParser;
import com.android.tradefed.command.CommandFileParser.CommandLine;
import com.android.tradefed.command.CommandOptions;
+import com.android.tradefed.command.CommandScheduler;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.ConfigurationFactory;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.SandboxConfigurationFactory;
+import com.android.tradefed.config.proxy.TradefedDelegator;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
@@ -147,6 +149,12 @@
String[] args = commands.get(i).asArray();
String cmdLine = QuotationAwareTokenizer.combineTokens(args);
try {
+ TradefedDelegator delegator = CommandScheduler.checkDelegation(args);
+ if (delegator.shouldUseDelegation()) {
+ // TODO: Add some validation of delegated config.
+ continue;
+ }
+
if (cmdLine.contains("--" + CommandOptions.USE_SANDBOX)) {
// Handle the sandboxed command use case.
testSandboxCommand(args);
diff --git a/src/com/android/tradefed/testtype/SubprocessTfLauncher.java b/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
index bbb8ea0..814ecf5 100644
--- a/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
+++ b/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
@@ -23,6 +23,7 @@
import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.error.HarnessRuntimeException;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
@@ -31,6 +32,7 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.result.proto.StreamProtoReceiver;
import com.android.tradefed.result.proto.StreamProtoResultReporter;
import com.android.tradefed.util.CommandResult;
@@ -409,11 +411,12 @@
if (result.getStatus().equals(CommandStatus.TIMED_OUT)) {
errMessage = String.format("Timeout after %s",
TimeUtil.formatElapsedTime(mMaxTfRunTime));
- throw new RuntimeException(
+ throw new HarnessRuntimeException(
String.format(
"%s Tests subprocess failed due to:\n%s\n",
- mConfigName, errMessage));
- } else {
+ mConfigName, errMessage),
+ InfraErrorIdentifier.INVOCATION_TIMEOUT);
+ } else if (eventParser != null && !eventParser.reportedInvocationFailed()) {
SubprocessExceptionParser.handleStderrException(result);
}
}
diff --git a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
index 5c6348d..9ded188 100644
--- a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
@@ -284,6 +284,8 @@
if (mEnableMainlineParameter) {
mModuleRepo.setMainlineParameterizedModules(mEnableMainlineParameter);
mModuleRepo.setInvocationContext(getInvocationContext());
+ mModuleRepo.setOptimizeMainlineTest(
+ getConfiguration().getCommandOptions().getOptimizeMainlineTest());
}
mModuleRepo.setParameterizedModules(mEnableParameter);
diff --git a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
index 5f00853..5400ddd 100644
--- a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
+++ b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
@@ -17,6 +17,7 @@
package com.android.tradefed.testtype.suite;
import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.metric.CollectorHelper;
@@ -51,27 +52,28 @@
* A wrapper class works on the {@link IRemoteTest} to granulate the IRemoteTest in testcase level.
* An IRemoteTest can contain multiple testcases. Previously, these testcases are treated as a
* whole: When IRemoteTest runs, all testcases will run. Some IRemoteTest (The ones that implements
- * ITestFilterReceiver) can accept a whitelist of testcases and only run those testcases. This class
- * takes advantage of the existing feature and provides a more flexible way to run test suite.
+ * ITestFilterReceiver) can accept an allowlist of testcases and only run those testcases. This
+ * class takes advantage of the existing feature and provides a more flexible way to run test suite.
*
* <ul>
- * <li> Single testcase can be retried multiple times (within the same IRemoteTest run) to reduce
+ * <li>Single testcase can be retried multiple times (within the same IRemoteTest run) to reduce
* the non-test-error failure rates.
- * <li> The retried testcases are dynamically collected from previous run failures.
+ * <li>The retried testcases are dynamically collected from previous run failures.
* </ul>
*
* <p>Note:
*
* <ul>
- * <li> The prerequisite to run a subset of test cases is that the test type should implement the
+ * <li>The prerequisite to run a subset of test cases is that the test type should implement the
* interface {@link ITestFilterReceiver}.
- * <li> X is customized max retry number.
+ * <li>X is customized max retry number.
* </ul>
*/
public class GranularRetriableTestWrapper implements IRemoteTest, ITestCollector {
private IRetryDecision mRetryDecision;
private IRemoteTest mTest;
+ private ModuleDefinition mModule;
private List<IMetricCollector> mRunMetricCollectors;
private TestFailureListener mFailureListener;
private IInvocationContext mModuleInvocationContext;
@@ -94,7 +96,18 @@
TestFailureListener failureListener,
List<ITestInvocationListener> moduleLevelListeners,
int maxRunLimit) {
+ this(test, null, mainListener, failureListener, moduleLevelListeners, maxRunLimit);
+ }
+
+ public GranularRetriableTestWrapper(
+ IRemoteTest test,
+ ModuleDefinition module,
+ ITestInvocationListener mainListener,
+ TestFailureListener failureListener,
+ List<ITestInvocationListener> moduleLevelListeners,
+ int maxRunLimit) {
mTest = test;
+ mModule = module;
mMainGranularRunListener = new ModuleListener(mainListener);
mFailureListener = failureListener;
mModuleLevelListeners = moduleLevelListeners;
@@ -194,6 +207,9 @@
if (collector.isDisabled()) {
CLog.d("%s has been disabled. Skipping.", collector);
} else {
+ if (collector instanceof IConfigurationReceiver) {
+ ((IConfigurationReceiver) collector).setConfiguration(mModuleConfiguration);
+ }
runListener = collector.init(mModuleInvocationContext, runListener);
}
}
@@ -226,7 +242,7 @@
// Bail out early if there is no need to retry at all.
if (!mRetryDecision.shouldRetry(
- mTest, 0, mMainGranularRunListener.getTestRunForAttempts(0))) {
+ mTest, mModule, 0, mMainGranularRunListener.getTestRunForAttempts(0))) {
return;
}
// Avoid rechecking the shouldRetry below the first time as it could retrigger reboot.
@@ -241,6 +257,7 @@
boolean retry =
mRetryDecision.shouldRetry(
mTest,
+ mModule,
attemptNumber - 1,
mMainGranularRunListener.getTestRunForAttempts(
attemptNumber - 1));
@@ -298,7 +315,10 @@
+ "successful, proceeding with next module. Stack trace:");
CLog.w(due);
CLog.w("Proceeding to the next test.");
- runListener.testRunFailed(createFromException(due));
+ // If it already was marked as failure do not remark it.
+ if (!mMainGranularRunListener.hasLastAttemptFailed()) {
+ runListener.testRunFailed(createFromException(due));
+ }
} catch (DeviceNotAvailableException dnae) {
// TODO: See if it's possible to report IReportNotExecuted
CLog.e("Run in progress was not completed due to:");
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index 3561868..3183e9c 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -69,13 +69,17 @@
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.google.inject.Inject;
import com.google.inject.Injector;
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -131,6 +135,9 @@
private static final String PRODUCT_CPU_ABI_KEY = "ro.product.cpu.abi";
+ private static final Set<String> ALLOWED_PREPARERS_CONFIGS =
+ ImmutableSet.of("/suite/allowed-preparers.txt", "/suite/google-allowed-preparers.txt");
+
// Options for test failure case
@Option(
name = "bugreport-on-failure",
@@ -509,6 +516,9 @@
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());
@@ -519,6 +529,7 @@
config.getKey(),
config.getValue().getTests(),
preparersPerDevice,
+ suitePreparersPerDevice,
config.getValue().getMultiTargetPreparers(),
config.getValue());
if (mDisableAutoRetryTimeReporting) {
@@ -585,6 +596,40 @@
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.
@@ -948,6 +993,7 @@
ModuleSplitter.splitConfiguration(
testInfo,
runConfig,
+ getAllowedPreparerPerDevice(mMainConfiguration),
shardCountHint,
mShouldMakeDynamicModule,
mIntraModuleSharding);
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index 0372eb7..2157877 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -59,6 +59,7 @@
import com.android.tradefed.result.TestRunResult;
import com.android.tradefed.result.error.ErrorIdentifier;
import com.android.tradefed.result.error.InfraErrorIdentifier;
+import com.android.tradefed.result.error.TestErrorIdentifier;
import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.retry.IRetryDecision;
import com.android.tradefed.retry.RetryStatistics;
@@ -125,10 +126,13 @@
private IConfiguration mInternalTestConfiguration;
private IConfiguration mInternalTargetPreparerConfiguration;
private ILogSaver mLogSaver;
+ private TestInformation mModuleInfo;
+ private ITestInvocationListener mInvocationListener;
private final String mId;
private Collection<IRemoteTest> mTests = null;
private Map<String, List<ITargetPreparer>> mPreparersPerDevice = null;
+ private Map<String, List<ITargetPreparer>> mSuitePreparersPerDevice = null;
private List<IMultiTargetPreparer> mMultiPreparers = new ArrayList<>();
private IBuildInfo mBuild;
@@ -174,6 +178,24 @@
Map<String, List<ITargetPreparer>> preparersPerDevice,
List<IMultiTargetPreparer> multiPreparers,
IConfiguration moduleConfig) {
+ this(name, tests, preparersPerDevice, null, multiPreparers, moduleConfig);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param name unique name of the test configuration.
+ * @param tests list of {@link IRemoteTest} that needs to run.
+ * @param preparersPerDevice list of {@link ITargetPreparer} to be used to setup the device.
+ * @param moduleConfig the {@link IConfiguration} of the underlying module config.
+ */
+ public ModuleDefinition(
+ String name,
+ Collection<IRemoteTest> tests,
+ Map<String, List<ITargetPreparer>> preparersPerDevice,
+ Map<String, List<ITargetPreparer>> suitePreparersPerDevice,
+ List<IMultiTargetPreparer> multiPreparers,
+ IConfiguration moduleConfig) {
mId = name;
mTests = tests;
mModuleConfiguration = moduleConfig;
@@ -204,6 +226,7 @@
mMultiPreparers.addAll(multiPreparers);
mPreparersPerDevice = preparersPerDevice;
+ mSuitePreparersPerDevice = suitePreparersPerDevice;
// Get the tokens of the module
List<String> tokens = configDescriptor.getMetaData(ITestSuite.TOKEN_KEY);
@@ -343,6 +366,9 @@
TestFailureListener failureListener,
int maxRunLimit)
throws DeviceNotAvailableException {
+ mModuleInfo = moduleInfo;
+ mInvocationListener = listener;
+
mStartModuleRunDate = System.currentTimeMillis();
// Load extra configuration for the module from module_controller
// TODO: make module_controller a full TF object
@@ -390,22 +416,10 @@
moduleInfo.getDevice(), mInternalTargetPreparerConfiguration);
}
// Setup
- long prepStartTime = getCurrentTime();
if (preparationException == null) {
- preparationException = runTargetPreparation(moduleInfo, listener);
+ preparationException = runPreparation(false);
}
- // Skip multi-preparation if preparation already failed.
- if (preparationException == null) {
- for (IMultiTargetPreparer multiPreparer : mMultiPreparers) {
- preparationException = runMultiPreparerSetup(multiPreparer, moduleInfo, listener);
- if (preparationException != null) {
- mIsFailedModule = true;
- CLog.e("Some preparation step failed. failing the module %s", getId());
- break;
- }
- }
- }
- mElapsedPreparation = getCurrentTime() - prepStartTime;
+
// Run the tests
try {
if (preparationException != null) {
@@ -509,7 +523,13 @@
// After the run, if the test failed (even after retry the final result passed) has
// failed, capture a bugreport.
if (retriableTest.getResultListener().hasLastAttemptFailed()) {
- captureBugreport(listener, getId());
+ captureBugreport(
+ listener,
+ getId(),
+ retriableTest
+ .getResultListener()
+ .getCurrentRunResults()
+ .getRunFailureDescription());
}
}
} finally {
@@ -598,7 +618,7 @@
int maxRunLimit) {
GranularRetriableTestWrapper retriableTest =
new GranularRetriableTestWrapper(
- test, listener, failureListener, moduleLevelListeners, maxRunLimit);
+ test, this, listener, failureListener, moduleLevelListeners, maxRunLimit);
retriableTest.setModuleId(getId());
retriableTest.setMarkTestsSkipped(skipTestCases);
retriableTest.setMetricCollectors(mRunMetricCollectors);
@@ -609,7 +629,13 @@
return retriableTest;
}
- private void captureBugreport(ITestLogger listener, String moduleId) {
+ private void captureBugreport(
+ ITestLogger listener, String moduleId, FailureDescription failure) {
+ FailureStatus status = failure.getFailureStatus();
+ if (!FailureStatus.LOST_SYSTEM_UNDER_TEST.equals(status)
+ && !FailureStatus.SYSTEM_UNDER_TEST_CRASHED.equals(status)) {
+ return;
+ }
for (ITestDevice device : mModuleInvocationContext.getDevices()) {
if (device.getIDevice() instanceof StubDevice) {
continue;
@@ -735,7 +761,7 @@
listener.testStarted(testEntry.getKey(), testEntry.getValue().getStartTime());
switch (testEntry.getValue().getStatus()) {
case FAILURE:
- listener.testFailed(testEntry.getKey(), testEntry.getValue().getStackTrace());
+ listener.testFailed(testEntry.getKey(), testEntry.getValue().getFailure());
break;
case ASSUMPTION_FAILURE:
listener.testAssumptionFailure(
@@ -746,7 +772,10 @@
break;
case INCOMPLETE:
listener.testFailed(
- testEntry.getKey(), "Test did not complete due to exception.");
+ testEntry.getKey(),
+ FailureDescription.create(
+ "Test did not complete due to exception.",
+ FailureStatus.TEST_FAILURE));
break;
default:
break;
@@ -766,11 +795,41 @@
}
}
+ /**
+ * Run preparers of the test, including suite level preparers if specified.
+ *
+ * @param includeSuitePreparers Set to {@code true} to also run suite level preparers.
+ * @return {@link Throwable} of any exception raised when running preparers.
+ */
+ public Throwable runPreparation(boolean includeSuitePreparers) {
+ Throwable preparationException = null;
+ long prepStartTime = getCurrentTime();
+ if (includeSuitePreparers) {
+ // Run suite level preparers.
+ preparationException = runTargetPreparation(mSuitePreparersPerDevice);
+ }
+
+ if (preparationException == null) {
+ preparationException = runTargetPreparation(mPreparersPerDevice);
+ }
+ // Skip multi-preparation if preparation already failed.
+ if (preparationException == null) {
+ for (IMultiTargetPreparer multiPreparer : mMultiPreparers) {
+ preparationException = runMultiPreparerSetup(multiPreparer);
+ if (preparationException != null) {
+ mIsFailedModule = true;
+ CLog.e("Some preparation step failed. failing the module %s", getId());
+ break;
+ }
+ }
+ }
+ mElapsedPreparation = getCurrentTime() - prepStartTime;
+ return preparationException;
+ }
+
/** Run all the prepare steps. */
private Throwable runPreparerSetup(
- TestInformation moduleInfo,
ITargetPreparer preparer,
- ITestLogger logger,
int deviceIndex) {
if (preparer.isDisabled()) {
// If disabled skip completely.
@@ -781,14 +840,14 @@
try {
// set the logger in case they need it.
if (preparer instanceof ITestLoggerReceiver) {
- ((ITestLoggerReceiver) preparer).setTestLogger(logger);
+ ((ITestLoggerReceiver) preparer).setTestLogger(mInvocationListener);
}
if (preparer instanceof IInvocationContextReceiver) {
((IInvocationContextReceiver) preparer)
.setInvocationContext(mModuleInvocationContext);
}
- moduleInfo.setActiveDeviceIndex(deviceIndex);
- preparer.setUp(moduleInfo);
+ mModuleInfo.setActiveDeviceIndex(deviceIndex);
+ preparer.setUp(mModuleInfo);
return null;
} catch (BuildError
| TargetSetupError
@@ -802,13 +861,12 @@
CLog.e(e);
return e;
} finally {
- moduleInfo.setActiveDeviceIndex(0);
+ mModuleInfo.setActiveDeviceIndex(0);
}
}
/** Run all multi target preparer step. */
- private Throwable runMultiPreparerSetup(
- IMultiTargetPreparer preparer, TestInformation moduleInfo, ITestLogger logger) {
+ private Throwable runMultiPreparerSetup(IMultiTargetPreparer preparer) {
if (preparer.isDisabled()) {
// If disabled skip completely.
return null;
@@ -818,13 +876,13 @@
try {
// set the logger in case they need it.
if (preparer instanceof ITestLoggerReceiver) {
- ((ITestLoggerReceiver) preparer).setTestLogger(logger);
+ ((ITestLoggerReceiver) preparer).setTestLogger(mInvocationListener);
}
if (preparer instanceof IInvocationContextReceiver) {
((IInvocationContextReceiver) preparer)
.setInvocationContext(mModuleInvocationContext);
}
- preparer.setUp(moduleInfo);
+ preparer.setUp(mModuleInfo);
return null;
} catch (BuildError
| TargetSetupError
@@ -976,6 +1034,14 @@
}
/**
+ * Returns the list of suite level {@link ITargetPreparer} associated with the given device name
+ */
+ @VisibleForTesting
+ List<ITargetPreparer> getSuitePreparerForDevice(String deviceName) {
+ return mSuitePreparersPerDevice.get(deviceName);
+ }
+
+ /**
* When running unit tests for ModuleDefinition we don't want to unnecessarily report some auto
* retry times.
*/
@@ -996,7 +1062,9 @@
}
listener.testRunStarted(getId(), 0, 0, System.currentTimeMillis());
FailureDescription description =
- FailureDescription.create(message).setFailureStatus(FailureStatus.NOT_EXECUTED);
+ FailureDescription.create(message)
+ .setFailureStatus(FailureStatus.NOT_EXECUTED)
+ .setErrorIdentifier(TestErrorIdentifier.MODULE_DID_NOT_EXECUTE);
listener.testRunFailed(description);
listener.testRunEnded(0, new HashMap<String, Metric>());
listener.testModuleEnded();
@@ -1044,28 +1112,28 @@
InvocationMetricKey.AUTO_RETRY_TIME, retryTimeMs);
}
- private Throwable runTargetPreparation(TestInformation moduleInfo, ITestLogger logger) {
+ private Throwable runTargetPreparation(Map<String, List<ITargetPreparer>> preparersPerDevice) {
Throwable preparationException = null;
for (int i = 0; i < mModuleInvocationContext.getDeviceConfigNames().size(); i++) {
String deviceName = mModuleInvocationContext.getDeviceConfigNames().get(i);
- if (i >= mPreparersPerDevice.size()) {
+ if (i >= preparersPerDevice.size()) {
CLog.d(
"Main configuration has more devices than the module configuration. '%s' "
+ "will not run any preparation.",
deviceName);
continue;
}
- List<ITargetPreparer> preparers = mPreparersPerDevice.get(deviceName);
+ List<ITargetPreparer> preparers = preparersPerDevice.get(deviceName);
if (preparers == null) {
CLog.w(
"Module configuration devices mismatch the main configuration "
+ "(Missing device '%s'), resolving preparers by index.",
deviceName);
- String key = new ArrayList<>(mPreparersPerDevice.keySet()).get(i);
- preparers = mPreparersPerDevice.get(key);
+ String key = new ArrayList<>(preparersPerDevice.keySet()).get(i);
+ preparers = preparersPerDevice.get(key);
}
for (ITargetPreparer preparer : preparers) {
- preparationException = runPreparerSetup(moduleInfo, preparer, logger, i);
+ preparationException = runPreparerSetup(preparer, i);
if (preparationException != null) {
mIsFailedModule = true;
CLog.e("Some preparation step failed. failing the module %s", getId());
@@ -1123,6 +1191,11 @@
failureDescription.setErrorIdentifier(id);
failureDescription.setFailureStatus(id.status());
failureDescription.setOrigin(((IHarnessException) setupException).getOrigin());
+ } else if (setupException instanceof RuntimeException) {
+ // TODO: switch to customer_issue
+ failureDescription.setFailureStatus(FailureStatus.UNSET);
+ failureDescription.setErrorIdentifier(
+ InfraErrorIdentifier.MODULE_SETUP_RUNTIME_EXCEPTION);
} else {
failureDescription.setFailureStatus(FailureStatus.UNSET);
}
diff --git a/src/com/android/tradefed/testtype/suite/ModuleSplitter.java b/src/com/android/tradefed/testtype/suite/ModuleSplitter.java
index a4fa85f..970522a 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleSplitter.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleSplitter.java
@@ -63,6 +63,7 @@
*
* @param testInfo the current {@link TestInformation} to proceed with sharding.
* @param runConfig {@link LinkedHashMap} loaded from {@link ITestSuite#loadTests()}.
+ * @param suitePreparersPerDevice map of suite level preparers per test device.
* @param shardCount a shard count hint to help with sharding.
* @param dynamicModule Whether or not module can be shared in pool or must be independent
* (strict sharding).
@@ -72,6 +73,7 @@
public static List<ModuleDefinition> splitConfiguration(
TestInformation testInfo,
LinkedHashMap<String, IConfiguration> runConfig,
+ Map<String, List<ITargetPreparer>> suitePreparersPerDevice,
int shardCount,
boolean dynamicModule,
boolean intraModuleSharding) {
@@ -93,7 +95,8 @@
configMap.getValue(),
shardCount,
dynamicModule,
- intraModuleSharding);
+ intraModuleSharding,
+ suitePreparersPerDevice);
} catch (RuntimeException e) {
CLog.e("Exception while creating module for '%s'", configMap.getKey());
throw e;
@@ -109,7 +112,8 @@
IConfiguration config,
int shardCount,
boolean dynamicModule,
- boolean intraModuleSharding) {
+ boolean intraModuleSharding,
+ Map<String, List<ITargetPreparer>> suitePreparersPerDevice) {
List<IRemoteTest> tests = config.getTests();
// Get rid of the IRemoteTest reference on the shared configuration. It will not be used
// to run.
@@ -127,11 +131,13 @@
moduleName,
tests,
clonePreparersMap(config),
+ clonePreparersMap(suitePreparersPerDevice),
clonePreparers(config.getMultiTargetPreparers()),
config);
currentList.add(module);
} else {
- addModuleToListFromSingleTest(currentList, tests.get(i), moduleName, config);
+ addModuleToListFromSingleTest(
+ currentList, tests.get(i), moduleName, config, suitePreparersPerDevice);
}
}
clearPreparersFromConfig(config);
@@ -153,6 +159,7 @@
moduleName,
shardedTests,
clonePreparersMap(config),
+ clonePreparersMap(suitePreparersPerDevice),
clonePreparers(config.getMultiTargetPreparers()),
config);
currentList.add(module);
@@ -161,14 +168,19 @@
// We create independent modules with each sharded test.
for (IRemoteTest moduleTest : shardedTests) {
addModuleToListFromSingleTest(
- currentList, moduleTest, moduleName, config);
+ currentList,
+ moduleTest,
+ moduleName,
+ config,
+ suitePreparersPerDevice);
}
}
continue;
}
}
// test is not shardable or did not shard
- addModuleToListFromSingleTest(currentList, test, moduleName, config);
+ addModuleToListFromSingleTest(
+ currentList, test, moduleName, config, suitePreparersPerDevice);
}
clearPreparersFromConfig(config);
}
@@ -181,7 +193,8 @@
List<ModuleDefinition> currentList,
IRemoteTest test,
String moduleName,
- IConfiguration config) {
+ IConfiguration config,
+ Map<String, List<ITargetPreparer>> suitePreparersPerDevice) {
List<IRemoteTest> testList = new ArrayList<>();
testList.add(test);
ModuleDefinition module =
@@ -189,6 +202,7 @@
moduleName,
testList,
clonePreparersMap(config),
+ clonePreparersMap(suitePreparersPerDevice),
clonePreparers(config.getMultiTargetPreparers()),
config);
currentList.add(module);
@@ -234,6 +248,18 @@
return res;
}
+ /** Deep cloning of potentially multi-device preparers. */
+ private static Map<String, List<ITargetPreparer>> clonePreparersMap(
+ Map<String, List<ITargetPreparer>> suitePreparersPerDevice) {
+ Map<String, List<ITargetPreparer>> res = new LinkedHashMap<>();
+ for (String device : suitePreparersPerDevice.keySet()) {
+ List<ITargetPreparer> preparers = new ArrayList<>();
+ res.put(device, preparers);
+ preparers.addAll(clonePreparers(suitePreparersPerDevice.get(device)));
+ }
+ return res;
+ }
+
private static void clearPreparersFromConfig(IConfiguration config) {
try {
for (IDeviceConfiguration holder : config.getDeviceConfig()) {
diff --git a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
index 26a7f4a..2831e8b 100644
--- a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
+++ b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
@@ -79,6 +79,7 @@
private boolean mAllowParameterizedModules = false;
private boolean mAllowMainlineParameterizedModules = false;
+ private boolean mOptimizeMainlineTest = false;
private boolean mAllowOptionalParameterizedModules = false;
private ModuleParameters mForcedModuleParameter = null;
private Set<ModuleParameters> mExcludedModuleParameters = new HashSet<>();
@@ -121,6 +122,11 @@
mAllowMainlineParameterizedModules = allowed;
}
+ /** Sets whether or not to optimize mainline test. */
+ public final void setOptimizeMainlineTest(boolean allowed) {
+ mOptimizeMainlineTest = allowed;
+ }
+
/** Sets whether or not to allow optional parameterized modules. */
public final void setOptionalParameterizedModules(boolean allowed) {
mAllowOptionalParameterizedModules = allowed;
@@ -378,7 +384,8 @@
new MainlineModuleHandler(
param,
abi,
- mContext
+ mContext,
+ mOptimizeMainlineTest
);
skipCreatingBaseConfig = true;
IConfiguration paramConfig =
diff --git a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
index da1741f..6082517 100644
--- a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
+++ b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
@@ -219,9 +219,9 @@
if (configPath == null) {
throw new RuntimeException(String.format("Configuration path is null."));
}
- File configFie = new File(configPath);
- if (!configFie.exists()) {
- configFie = null;
+ File configFile = new File(configPath);
+ if (!configFile.exists()) {
+ configFile = null;
}
// De-duplicate test infos so that there won't be duplicate test options.
testInfos = dedupTestInfos(testInfos);
@@ -229,10 +229,10 @@
// Clean up all the test options injected in SuiteModuleLoader.
super.cleanUpSuiteSetup();
super.clearModuleArgs();
- if (configFie != null) {
+ if (configFile != null) {
clearConfigPaths();
// Set config path to BaseTestSuite to limit the search.
- addConfigPaths(configFie);
+ addConfigPaths(configFile);
}
// Inject the test options from each test info to SuiteModuleLoader.
parseOptions(testInfo);
diff --git a/src/com/android/tradefed/testtype/suite/module/Sdk30ModuleController.java b/src/com/android/tradefed/testtype/suite/module/Sdk30ModuleController.java
new file mode 100644
index 0000000..13ca021
--- /dev/null
+++ b/src/com/android/tradefed/testtype/suite/module/Sdk30ModuleController.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 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.module;
+
+/**
+ * Only run tests if the device under test is SDK version 30 or above.
+ *
+ * <p>Use by adding this line to your AndroidTest.xml: <object type="module_controller"
+ * class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+ */
+public class Sdk30ModuleController extends MinSdkModuleController {
+ public Sdk30ModuleController() {
+ super(30);
+ }
+}
diff --git a/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java b/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java
index eb4f4a4..9f4af97 100644
--- a/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java
+++ b/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java
@@ -38,11 +38,17 @@
private String mDynamicBaseLink = null;
private IAbi mAbi = null;
private String mName = null;
+ private boolean mOptimizeMainlineTest = false;
- public MainlineModuleHandler(String name, IAbi abi, IInvocationContext context) {
+ public MainlineModuleHandler(
+ String name,
+ IAbi abi,
+ IInvocationContext context,
+ boolean optimize) {
mName = name;
mAbi = abi;
buildDynamicBaseLink(context.getBuildInfos().get(0));
+ mOptimizeMainlineTest = optimize;
}
/** Builds the dynamic base link where the mainline modules would be downloaded. */
@@ -78,6 +84,7 @@
private InstallApexModuleTargetPreparer createMainlineModuleInstaller() {
InstallApexModuleTargetPreparer mainlineModuleInstaller =
new InstallApexModuleTargetPreparer();
+ mainlineModuleInstaller.setSkipApexTearDown(mOptimizeMainlineTest);
// Inject the real dynamic link to the target preparer so that it will dynamically download
// the mainline modules.
String fullDynamicLink = mDynamicBaseLink;
diff --git a/src/com/android/tradefed/util/AaptParser.java b/src/com/android/tradefed/util/AaptParser.java
index a88fb8f..a61d230 100644
--- a/src/com/android/tradefed/util/AaptParser.java
+++ b/src/com/android/tradefed/util/AaptParser.java
@@ -54,6 +54,48 @@
private static final int AAPT_TIMEOUT_MS = 60000;
private static final int INVALID_SDK = -1;
+ /**
+ * Enum of options for AAPT version used to parse APK files.
+ */
+ public static enum AaptVersion {
+ AAPT {
+ @Override
+ public String[] dumpBadgingCommand(File apkFile) {
+ return new String[] {"aapt", "dump", "badging", apkFile.getAbsolutePath()};
+ }
+
+ @Override
+ public String[] dumpXmlTreeCommand(File apkFile) {
+ return new String[] {
+ "aapt", "dump", "xmltree", apkFile.getAbsolutePath(), "AndroidManifest.xml"
+ };
+ }
+ },
+
+ AAPT2 {
+ @Override
+ public String[] dumpBadgingCommand(File apkFile) {
+ return new String[] {"aapt2", "dump", "badging", apkFile.getAbsolutePath()};
+ }
+
+ @Override
+ public String[] dumpXmlTreeCommand(File apkFile) {
+ return new String[] {
+ "aapt2",
+ "dump",
+ "xmltree",
+ apkFile.getAbsolutePath(),
+ "--file",
+ "AndroidManifest.xml"
+ };
+ }
+ };
+
+ public abstract String[] dumpBadgingCommand(File apkFile);
+
+ public abstract String[] dumpXmlTreeCommand(File apkFile);
+ };
+
private String mPackageName;
private String mVersionCode;
private String mVersionName;
@@ -130,16 +172,21 @@
* @return the {@link AaptParser} or <code>null</code> if failed to extract the information
*/
public static AaptParser parse(File apkFile) {
+ return parse(apkFile, AaptVersion.AAPT);
+ }
+
+ /**
+ * Parse info from the apk.
+ *
+ * @param apkFile the apk file
+ * @param aaptVersion the aapt version
+ * @return the {@link AaptParser} or <code>null</code> if failed to extract the information
+ */
+ public static AaptParser parse(File apkFile, AaptVersion aaptVersion) {
CommandResult result =
RunUtil.getDefault()
.runTimedCmdRetry(
- AAPT_TIMEOUT_MS,
- 0L,
- 2,
- "aapt",
- "dump",
- "badging",
- apkFile.getAbsolutePath());
+ AAPT_TIMEOUT_MS, 0L, 2, aaptVersion.dumpBadgingCommand(apkFile));
String stderr = result.getStderr();
if (stderr != null && !stderr.isEmpty()) {
@@ -158,11 +205,7 @@
AAPT_TIMEOUT_MS,
0L,
2,
- "aapt",
- "dump",
- "xmltree",
- apkFile.getAbsolutePath(),
- "AndroidManifest.xml");
+ aaptVersion.dumpXmlTreeCommand(apkFile));
stderr = result.getStderr();
if (stderr != null && !stderr.isEmpty()) {
diff --git a/src/com/android/tradefed/util/AdbRootElevator.java b/src/com/android/tradefed/util/AdbRootElevator.java
new file mode 100644
index 0000000..fa6eb0c
--- /dev/null
+++ b/src/com/android/tradefed/util/AdbRootElevator.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.INativeDevice;
+
+/**
+ * An {@link java.lang.AutoCloseable} that enables adb root when constructed if needed and restores
+ * root state when complete.
+ */
+public class AdbRootElevator implements AutoCloseable {
+ private final boolean mWasRoot;
+ private final INativeDevice mDevice;
+
+ public AdbRootElevator(INativeDevice device) throws DeviceNotAvailableException {
+ mDevice = device;
+ mWasRoot = mDevice.isAdbRoot();
+ if (!mWasRoot) {
+ if (!mDevice.enableAdbRoot()) {
+ throw new RuntimeException("Failed to enable adb root.");
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ if (!mWasRoot) {
+ try {
+ mDevice.disableAdbRoot();
+ } catch (DeviceNotAvailableException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/src/com/android/tradefed/util/GCSFileDownloader.java b/src/com/android/tradefed/util/GCSFileDownloader.java
index 1e81adb..5d039e5 100644
--- a/src/com/android/tradefed/util/GCSFileDownloader.java
+++ b/src/com/android/tradefed/util/GCSFileDownloader.java
@@ -304,8 +304,12 @@
}
} while (true);
} catch (IOException e) {
- CLog.e("Failed to download gs://%s/%s, clean up.", bucketName, remoteFilename);
- throw new BuildRetrievalError(e.getMessage(), e);
+ String message =
+ String.format(
+ "Failed to download gs://%s/%s due to: %s",
+ bucketName, remoteFilename, e.getMessage());
+ CLog.e(message);
+ throw new BuildRetrievalError(message, e, InfraErrorIdentifier.GCS_ERROR);
}
}
diff --git a/src/com/android/tradefed/util/ProtoUtil.java b/src/com/android/tradefed/util/ProtoUtil.java
new file mode 100644
index 0000000..bf1699a
--- /dev/null
+++ b/src/com/android/tradefed/util/ProtoUtil.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+import com.android.tradefed.log.LogUtil.CLog;
+
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Message;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Utility methods for dealing with protobuf messages type-agnostically. */
+public class ProtoUtil {
+
+ /**
+ * Get values of a nested field reference, i.e. field_1.field_2.field_3, from a proto message as
+ * a list of strings. Returns an empty list when a field cannot be found.
+ *
+ * <p>If the field reference contains repeated fields, each instance is expanded, resulting in a
+ * list of strings.
+ *
+ * @param message The protobuf {@link Message} or object to be parsed.
+ * @param references A list of field references starting at the root of the message. e.g. if we
+ * want to read {@code field_2} under the value of {@code field_1} in {@code
+ * messageOrObject} the list would be {@code field1}, {@code field2}.
+ * @return A list of all the fields values referred to by the reference. If {@code references}
+ * is empty, returns {@code message.toString()} as a list. If {@code references} is invalid,
+ * returns an empty list.
+ */
+ public static List<String> getNestedFieldFromMessageAsStrings(
+ Message message, List<String> references) {
+ return getNestedFieldFromMessageAsStringsHelper(message, references);
+ }
+
+ /**
+ * A helper method to {@code getNestedFieldFromMessageAsStrings} where the "message" can be an
+ * object in case we reach a primitive value field during recursive parsing.
+ */
+ private static List<String> getNestedFieldFromMessageAsStringsHelper(
+ Object messageOrObject, List<String> references) {
+ if (references.isEmpty()) {
+ return Arrays.asList(String.valueOf(messageOrObject));
+ }
+ if (!(messageOrObject instanceof Message)) {
+ CLog.e(
+ "Attempting to read field %s from object of type %s, "
+ + "which is not a proto message.",
+ references.get(0), messageOrObject.getClass());
+ return new ArrayList<String>();
+ }
+ Message message = (Message) messageOrObject;
+ String reference = references.get(0);
+ FieldDescriptor fieldDescriptor = message.getDescriptorForType().findFieldByName(reference);
+ if (fieldDescriptor == null) {
+ CLog.e("Could not find field %s in message %s.", reference, message);
+ return new ArrayList<String>();
+ }
+ Object fieldValue = message.getField(fieldDescriptor);
+ if (fieldValue instanceof List) {
+ return ((List<? extends Object>) fieldValue)
+ .stream()
+ .flatMap(
+ v ->
+ getNestedFieldFromMessageAsStringsHelper(
+ v, references.subList(1, references.size()))
+ .stream())
+ .collect(Collectors.toList());
+ }
+ return getNestedFieldFromMessageAsStringsHelper(
+ fieldValue, references.subList(1, references.size()));
+ }
+}
diff --git a/src/com/android/tradefed/util/StringEscapeUtils.java b/src/com/android/tradefed/util/StringEscapeUtils.java
index 846c0e7..4cc5741 100644
--- a/src/com/android/tradefed/util/StringEscapeUtils.java
+++ b/src/com/android/tradefed/util/StringEscapeUtils.java
@@ -47,6 +47,15 @@
case '\\':
out.append("\\\\");
break;
+ case '>':
+ out.append("\\>");
+ break;
+ case '<':
+ out.append("\\<");
+ break;
+ case '|':
+ out.append("\\|");
+ break;
default:
out.append(ch);
break;
diff --git a/src/com/android/tradefed/util/SubprocessTestResultsParser.java b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
index 2a5d280..c560e6a 100644
--- a/src/com/android/tradefed/util/SubprocessTestResultsParser.java
+++ b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
@@ -80,6 +80,7 @@
private TestDescription mCurrentTest = null;
private IInvocationContext mCurrentModuleContext = null;
+ private InvocationFailedEventInfo mReportedInvocationFailedEventInfo = null;
private Pattern mPattern = null;
private Map<String, EventHandler> mHandlerMap = null;
@@ -423,6 +424,7 @@
} else {
mListener.invocationFailed(ifi.mCause);
}
+ mReportedInvocationFailedEventInfo = ifi;
}
}
@@ -653,4 +655,14 @@
public TestDescription getCurrentTest() {
return mCurrentTest;
}
+
+ /** Returns whether or not an invocation failed was reported. */
+ public boolean reportedInvocationFailed() {
+ return (mReportedInvocationFailedEventInfo != null);
+ }
+
+ /** Returns reported invocation failure event info. */
+ public InvocationFailedEventInfo getReportedInvocationFailedEventInfo() {
+ return mReportedInvocationFailedEventInfo;
+ }
}
diff --git a/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java b/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java
index c176bdb..7a94bf5 100644
--- a/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java
+++ b/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java
@@ -15,28 +15,27 @@
*/
package com.android.tradefed.util.executor;
-import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-/** Wrapper of {@link ExecutorService} to execute a function on all devices in parallel. */
+/** Wrapper of {@link ExecutorService} to execute a function in parallel. */
public class ParallelDeviceExecutor<V> {
- private List<ITestDevice> mDevices;
+ private final int mPoolSize;
private List<Throwable> mErrors;
- public ParallelDeviceExecutor(List<ITestDevice> devices) {
- mDevices = devices;
+ public ParallelDeviceExecutor(int poolSize) {
+ mPoolSize = poolSize;
mErrors = new ArrayList<>();
}
@@ -44,14 +43,14 @@
* Invoke all the {@link Callable} with the timeout limit.
*
* @param callableTasks The List of tasks.
- * @param timeout The timeout to apply.
+ * @param timeout The timeout to apply, or zero for unlimited.
* @param unit The unit of the timeout.
* @return The list of results for each callable task.
*/
public List<V> invokeAll(List<Callable<V>> callableTasks, long timeout, TimeUnit unit) {
ExecutorService executor =
Executors.newFixedThreadPool(
- mDevices.size(),
+ mPoolSize,
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
@@ -62,12 +61,15 @@
});
List<V> results = new ArrayList<>();
try {
- List<Future<V>> futures = executor.invokeAll(callableTasks);
+ List<Future<V>> futures =
+ timeout == 0L
+ ? executor.invokeAll(callableTasks)
+ : executor.invokeAll(callableTasks, timeout, unit);
for (Future<V> future : futures) {
try {
- results.add(future.get(timeout, unit));
- } catch (TimeoutException timeoutException) {
- mErrors.add(timeoutException);
+ results.add(future.get());
+ } catch (CancellationException cancellationException) {
+ mErrors.add(cancellationException);
} catch (ExecutionException execException) {
mErrors.add(execException.getCause());
}
diff --git a/test_framework/com/android/tradefed/device/metric/AtraceCollector.java b/test_framework/com/android/tradefed/device/metric/AtraceCollector.java
index c6ea63b..5a32035 100644
--- a/test_framework/com/android/tradefed/device/metric/AtraceCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/AtraceCollector.java
@@ -27,6 +27,7 @@
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
@@ -268,7 +269,9 @@
postProcess(trace);
trace.delete();
} else {
- throw new DeviceRuntimeException("failed to pull log: " + fullLogPath());
+ throw new DeviceRuntimeException(
+ String.format("failed to pull log: %s", fullLogPath()),
+ DeviceErrorIdentifier.FAIL_PULL_FILE);
}
if (!mPreserveOndeviceLog) {
diff --git a/test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java
deleted file mode 100644
index 304985e..0000000
--- a/test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect fragmentation at regular intervals. */
-public class BuddyInfoMetricCollector extends ScheduledDeviceMetricCollector {
- public BuddyInfoMetricCollector() {
- setTag("fragmentation");
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- CLog.i("Running unusable-index collector...");
- String outputFileName =
- String.format("%s/unusable-index-%s", createTempDir(), getFileSuffix());
- File outputFile =
- saveProcessOutput(device, "cat /d/extfrag/unusable_index", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.TEXT,
- source);
- }
-
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java b/test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java
deleted file mode 100644
index 8bf0dd6..0000000
--- a/test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect zipped bugreport at regular intervals. */
-public class BugreportzMetricCollector extends ScheduledDeviceMetricCollector {
- public BugreportzMetricCollector() {
- setTag("bugreportz");
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- CLog.i("Running bugreportz...");
-
- String hostBugreportFilename = String.format("bugreport-%s", getFileSuffix());
- if (!device.logBugreport(hostBugreportFilename, getInvocationListener())) {
- CLog.e("Failed to run bugreportz or bugreport.");
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java b/test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java
deleted file mode 100644
index 404580a..0000000
--- a/test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.loganalysis.item.CompactMemInfoItem;
-import com.android.loganalysis.parser.CompactMemInfoParser;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.util.FileUtil;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A {@link ScheduledDeviceMetricCollector} to collect memory dumps of processes at regular
- * intervals.
- */
-public class DumpHeapCollector extends ScheduledDeviceMetricCollector {
- private static final String DUMPHEAP_OUTPUT = "/data/local/tmp";
- private static final String SUFFIX = "trigger";
-
- @Option(
- name = "dumpheap-thresholds",
- description =
- "Threshold map for taking process dumpheaps. "
- + "The key should be the process name and its corresponding value is the "
- + "maximum acceptable heap size for that process."
- + "Note that to get heap dump for native and managed processes set their "
- + "threshold to 0."
- )
- protected Map<String, Long> mDumpheapThresholds = new HashMap<String, Long>();
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- CLog.i("Running dumpheap collection...");
- List<File> dumpFiles = new ArrayList<>();
- try {
- for (String process : mDumpheapThresholds.keySet()) {
- String output =
- device.executeShellCommand(
- String.format("dumpsys meminfo -c | grep %s", process));
-
- dumpFiles = takeDumpheap(device, output, process, mDumpheapThresholds.get(process));
-
- dumpFiles.forEach(dumpheap -> saveDumpheap(dumpheap));
- }
- } catch (DeviceNotAvailableException e) {
- CLog.e(e);
- } finally {
- dumpFiles.forEach(dumpFile -> FileUtil.deleteFile(dumpFile));
- }
- }
-
- /**
- * Collects heap dump for each requested process if the PSS is greater than a threshold.
- *
- * @param device
- * @param output of the meminfo command.
- * @param process for which we need the heap dump.
- * @param threshold which is the maximum tolerable PSS.
- * @return the list of {@link File}s in the host containing the report. Empty list if something
- * failed.
- * @throws DeviceNotAvailableException
- */
- @VisibleForTesting
- List<File> takeDumpheap(ITestDevice device, String output, String process, Long threshold)
- throws DeviceNotAvailableException {
- List<File> dumpFiles = new ArrayList<>();
- if (output.isEmpty()) {
- CLog.i("Skipping %s -- no process found.", process);
- return dumpFiles;
- }
-
- CompactMemInfoItem item =
- new CompactMemInfoParser().parse(Arrays.asList(output.split("\n")));
-
- for (Integer pid : item.getPids()) {
- if (item.getName(pid).equals(process) && item.getPss(pid) > threshold) {
- File dump = device.dumpHeap(process, getDevicePath(process));
- dumpFiles.add(dump);
- }
- }
- return dumpFiles;
- }
-
- /**
- * Returns the path on the device to put the dump.
- *
- * @param process for which dump is being requested.
- * @return a write-able path in device.
- */
- private String getDevicePath(String process) {
- return String.format(
- "%s/%s_%s_%s.hprof", DUMPHEAP_OUTPUT, process, SUFFIX, getFileSuffix());
- }
-
- @VisibleForTesting
- void saveDumpheap(File dumpheap) {
- if (dumpheap == null) {
- CLog.e("Failed to take dumpheap.");
- return;
- }
- try (FileInputStreamSource stream = new FileInputStreamSource(dumpheap)) {
- getInvocationListener()
- .testLog(FileUtil.getBaseName(dumpheap.getName()), LogDataType.HPROF, stream);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java b/test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java
deleted file mode 100644
index 8e73eda..0000000
--- a/test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.device.metric;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect graphics stats at regular intervals. */
-public class GraphicsStatsMetricCollector extends ScheduledDeviceMetricCollector {
- GraphicsStatsMetricCollector() {
- setTag("jank");
- }
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- CLog.i("Running graphicsstats...");
- String outputFileName =
- String.format("%s/graphics-%s", createTempDir(), getFileSuffix());
- File outputFile = saveProcessOutput(device, "dumpsys graphicsstats", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.GFX_INFO,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java
deleted file mode 100644
index 3312855..0000000
--- a/test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/**
- * A {@link ScheduledDeviceMetricCollector} to collect audio and system memory heaps at regular
- * intervals.
- */
-public class IonHeapInfoMetricCollector extends ScheduledDeviceMetricCollector {
- public IonHeapInfoMetricCollector() {
- setTag("ion");
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- collectIonAudio(device);
- collectIonSystem(device);
- }
-
- private void collectIonAudio(ITestDevice device) {
- try {
- CLog.i("Running ionheap audio collector...");
- String outputFileName =
- String.format("%s/ion-audio-%s", createTempDir(), getFileSuffix());
- File outputFile = saveProcessOutput(device, "cat /d/ion/heaps/audio", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.TEXT,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-
- private void collectIonSystem(ITestDevice device) {
- try {
- CLog.i("Running ionheap system collector...");
- String outputFileName =
- String.format("%s/ion-system-%s", createTempDir(), getFileSuffix());
- File outputFile = saveProcessOutput(device, "cat /d/ion/heaps/system", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.TEXT,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java
deleted file mode 100644
index 0890b74..0000000
--- a/test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.device.metric;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect memory dumps at regular intervals. */
-public class MemInfoMetricCollector extends ScheduledDeviceMetricCollector {
- MemInfoMetricCollector() {
- setTag("compact-meminfo");
- }
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- CLog.i("Running meminfo...");
- String outputFileName =
- String.format("%s/compact-meminfo-%s", createTempDir(), getFileSuffix());
- File outputFile = saveProcessOutput(device, "dumpsys meminfo -c -S", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.COMPACT_MEMINFO,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java
deleted file mode 100644
index 9ab0f33..0000000
--- a/test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect free page counts at regular intervals. */
-public class PagetypeInfoMetricCollector extends ScheduledDeviceMetricCollector {
- public PagetypeInfoMetricCollector() {
- setTag("pagetypeinfo");
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- CLog.i("Running pagetype info collector...");
- String outputFileName =
- String.format("%s/pagetypeinfo-%s", createTempDir(), getFileSuffix());
- File outputFile = saveProcessOutput(device, "cat /proc/pagetypeinfo", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.TEXT,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java b/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
index 257e9e5..4d496a4 100644
--- a/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
@@ -63,6 +63,8 @@
private static final String EXTRACTOR_SUCCESS = "1";
private static final String EXTRACTOR_FAILURE = "0";
private static final String EXTRACTOR_RUNTIME = "trace_extractor_runtime";
+ private static final String RAW_TRACE_FILE_SIZE = "perfetto_trace_file_size_bytes";
+ private static final String NSS_CACHE_ERROR = "base/nsscache-inl.h failed to lookup";
public enum METRIC_FILE_FORMAT {
text,
@@ -142,6 +144,11 @@
description = "Convert the raw trace file to perfetto metric file.")
private boolean mConvertToMetricFile = true;
+ @Option(name = "collect-perfetto-file-size",
+ description = "Set it to true to collect the perfetto file size as part"
+ + " of the metrics.")
+ private boolean mCollectPerfettoFileSize = false;
+
@Option(
name = "trace-processor-binary",
description = "Path to the trace processor shell. This will"
@@ -190,6 +197,15 @@
processSrcFile = decompressFile(metricFile);
}
+ // Update the file size metrics.
+ if (processSrcFile != null && mCollectPerfettoFileSize) {
+ double perfettoFileSizeInBytes = processSrcFile.length();
+ Metric.Builder metricDurationBuilder = Metric.newBuilder();
+ metricDurationBuilder.getMeasurementsBuilder().setSingleDouble(
+ perfettoFileSizeInBytes);
+ data.addMetric(RAW_TRACE_FILE_SIZE, metricDurationBuilder.setType(DataType.RAW));
+ }
+
// Convert to perfetto metric format.
if (mConvertToMetricFile) {
File convertedMetricFile = convertToMetricProto(processSrcFile);
@@ -234,9 +250,23 @@
String.format("%s_%s", mMetricPrefix, EXTRACTOR_RUNTIME),
metricDurationBuilder.setType(DataType.RAW));
- if (CommandStatus.SUCCESS.equals(cr.getStatus())) {
+ // Adding temporary workaround to handle the NSS cache error.
+ // TODO: Revert the NSS cache error handling after b/156924255 is fixed.
+ if (CommandStatus.SUCCESS.equals(cr.getStatus()) ||
+ (CommandStatus.FAILED.equals(cr.getStatus()) &&
+ cr.getStdout().contains(NSS_CACHE_ERROR))) {
String[] metrics = cr.getStdout().split(LINE_SEPARATOR);
+
+ boolean isMetricStarted = false;
for (String metric : metrics) {
+ // Skip until the first metric line is parsed.
+ // Usually "trace-durations-ms" is the first metric from the output.
+ if(isMetricStarted || metric.contains("trace-duration-ms")) {
+ isMetricStarted = true;
+ } else {
+ continue;
+ }
+
Pair<String, String> kv = splitKeyValue(metric);
if (kv != null) {
diff --git a/test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java b/test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java
deleted file mode 100644
index 5528177..0000000
--- a/test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import com.android.loganalysis.item.DumpsysProcessMeminfoItem;
-import com.android.loganalysis.parser.DumpsysProcessMeminfoParser;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.metrics.proto.MetricMeasurement.NumericValues;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/**
- * A {@link ScheduledDeviceMetricCollector} to measure peak memory usage of specified processes.
- * Collects PSS and USS (private dirty) memory usage values from dumpsys meminfo. The result will be
- * reported as a test run metric with key in the form of PSS#ProcName[#DeviceNum], in KB.
- */
-public class ProcessMaxMemoryCollector extends ScheduledDeviceMetricCollector {
-
- @Option(
- name = "memory-usage-process-name",
- description = "Process names (from `dumpsys meminfo`) to measure memory usage for"
- )
- private List<String> mProcessNames = new ArrayList<>();
-
- private class DeviceMemoryData {
- /** Peak PSS per process */
- private Map<String, Long> mProcPss = new HashMap<>();
- /** Peak USS per process */
- private Map<String, Long> mProcUss = new HashMap<>();
- }
-
- // Memory usage data per device
- private Map<ITestDevice, DeviceMemoryData> mMemoryData;
- private Map<ITestDevice, Map<String, NumericValues.Builder>> mPssMemoryPerProcess;
- private Map<ITestDevice, Map<String, NumericValues.Builder>> mUssMemoryPerProcess;
-
- @Override
- void onStart(DeviceMetricData runData) {
- mMemoryData = new HashMap<>();
- mPssMemoryPerProcess = new HashMap<>();
- mUssMemoryPerProcess = new HashMap<>();
-
- for (ITestDevice device : getDevices()) {
- mMemoryData.put(device, new DeviceMemoryData());
- mPssMemoryPerProcess.put(device, new HashMap<>());
- mUssMemoryPerProcess.put(device, new HashMap<>());
- }
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- Map<String, Long> procPss = mMemoryData.get(device).mProcPss;
- Map<String, Long> procUss = mMemoryData.get(device).mProcUss;
- for (String proc : mProcessNames) {
- String dumpResult = device.executeShellCommand("dumpsys meminfo --checkin " + proc);
- if (dumpResult.startsWith("No process found")) {
- // process not found, skip
- continue;
- }
- DumpsysProcessMeminfoItem item =
- new DumpsysProcessMeminfoParser()
- .parse(Arrays.asList(dumpResult.split("\n")));
- Long pss =
- item.get(DumpsysProcessMeminfoItem.TOTAL)
- .get(DumpsysProcessMeminfoItem.PSS);
- Long uss =
- item.get(DumpsysProcessMeminfoItem.TOTAL)
- .get(DumpsysProcessMeminfoItem.PRIVATE_DIRTY);
- if (pss == null || uss == null) {
- CLog.e("Error parsing meminfo output: " + dumpResult);
- continue;
- }
-
- // Track PSS values
- if (mPssMemoryPerProcess.get(device) == null) {
- mPssMemoryPerProcess.put(device, new HashMap<>());
- }
- if (mPssMemoryPerProcess.get(device).get(proc) == null) {
- mPssMemoryPerProcess.get(device).put(proc, NumericValues.newBuilder());
- }
- mPssMemoryPerProcess.get(device).get(proc).addNumericValue(pss);
-
- // Track USS values
- if (mUssMemoryPerProcess.get(device) == null) {
- mUssMemoryPerProcess.put(device, new HashMap<>());
- }
- if (mUssMemoryPerProcess.get(device).get(proc) == null) {
- mUssMemoryPerProcess.get(device).put(proc, NumericValues.newBuilder());
- }
- mUssMemoryPerProcess.get(device).get(proc).addNumericValue(uss);
-
- if (procPss.getOrDefault(proc, 0L) < pss) {
- procPss.put(proc, pss);
- }
- if (procUss.getOrDefault(proc, 0L) < uss) {
- procUss.put(proc, uss);
- }
- }
- } catch (DeviceNotAvailableException e) {
- CLog.e(e);
- }
- }
-
- @Override
- void onEnd(DeviceMetricData runData) {
- for (ITestDevice device : getDevices()) {
- // Report all the PSS data for each process
- for (Entry<String, NumericValues.Builder> values :
- mPssMemoryPerProcess.get(device).entrySet()) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(
- Measurements.newBuilder()
- .setNumericValues(values.getValue().build()))
- .build();
- metric.setUnit("kB").setType(DataType.RAW);
- runData.addMetricForDevice(device, "PSS#" + values.getKey(), metric);
- }
-
- // Report all the USS data for each process
- for (Entry<String, NumericValues.Builder> values :
- mUssMemoryPerProcess.get(device).entrySet()) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(
- Measurements.newBuilder()
- .setNumericValues(values.getValue().build()))
- .build();
- metric.setUnit("kB").setType(DataType.RAW);
- runData.addMetricForDevice(device, "USS#" + values.getKey(), metric);
- }
-
- // Continue reporting the max PSS / USS for compatibility
- Map<String, Long> procPss = mMemoryData.get(device).mProcPss;
- Map<String, Long> procUss = mMemoryData.get(device).mProcUss;
- for (Entry<String, Long> pss : procPss.entrySet()) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(
- Measurements.newBuilder().setSingleInt(pss.getValue()).build());
- metric.setUnit("kB").setType(DataType.PROCESSED);
- runData.addMetricForDevice(device, "MAX_PSS#" + pss.getKey(), metric);
- }
- for (Entry<String, Long> uss : procUss.entrySet()) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(
- Measurements.newBuilder().setSingleInt(uss.getValue()).build());
- metric.setUnit("kB").setType(DataType.PROCESSED);
- runData.addMetricForDevice(device, "MAX_USS#" + uss.getKey(), metric);
- }
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java
deleted file mode 100644
index 347a4b9..0000000
--- a/test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * 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.device.metric;
-
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ITestInvocationListener;
-
-import java.io.File;
-import java.lang.reflect.InvocationTargetException;
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * A {@link IMetricCollector} that makes runs multiple metric collectors periodically. This is a
- * best effort scheduler. It makes the best effort to run the collectors at given intervals while
- * making sure that no two collectors are run at the same time.
- */
-public class ScheduleMultipleDeviceMetricCollector extends BaseDeviceMetricCollector {
- @Option(
- name = "metric-collection-intervals",
- description = "The interval at which the collectors should run."
- )
- private Map<String, Long> mIntervalMs = new HashMap<>();
-
- @Option(
- name = "metric-storage-path",
- description =
- "Absolute path to a directory on host where the collected metrics will be stored."
- )
- private File mMetricStoragePath = new File(System.getProperty("java.io.tmpdir"));
-
- @Option(
- name = "metric-collector-command-classes",
- description =
- "Complete package name of a class which registers the commands to do the actual "
- + "job of collection. Can be repeated."
- )
- private List<String> mMetricCollectorClasses = new ArrayList<>();
-
- // List of collectors to run.
- private List<ScheduledDeviceMetricCollector> mMetricCollectors = new ArrayList<>();
-
- // Time interval at which the commands should run.
- private Map<ScheduledDeviceMetricCollector, Long> mMetricCollectorIntervals = new HashMap<>();
-
- // Time when the commands to collect various metrics were last run.
- private Map<ScheduledDeviceMetricCollector, Long> mLastUpdate = new HashMap<>();
-
- private Timer mTimer;
-
- private long mScheduleRate;
-
- @Override
- public ITestInvocationListener init(
- IInvocationContext context, ITestInvocationListener listener) {
- super.init(context, listener);
- initMetricCollectors(context, listener);
-
- return this;
- }
-
- /** Gets an instance of all the requested metric collectors. */
- private void initMetricCollectors(
- IInvocationContext context, ITestInvocationListener listener) {
- for (String metricCollectorClass : mMetricCollectorClasses) {
- try {
- Class<?> klass = Class.forName(metricCollectorClass);
-
- ScheduledDeviceMetricCollector singleMetricCollector =
- klass.asSubclass(ScheduledDeviceMetricCollector.class)
- .getDeclaredConstructor()
- .newInstance();
-
- singleMetricCollector.init(context, listener);
-
- mMetricCollectors.add(singleMetricCollector);
- } catch (ClassNotFoundException
- | InstantiationException
- | IllegalAccessException
- | InvocationTargetException
- | NoSuchMethodException e) {
- CLog.e("Class %s not found, skipping.", metricCollectorClass);
- CLog.e(e);
- }
- }
- }
-
- @Override
- public final void onTestRunStart(DeviceMetricData runData) {
- if (mMetricCollectorClasses.isEmpty()) {
- CLog.w("No single metric class provided. Skipping collection.");
- return;
- }
-
- setupCollection();
-
- if (mScheduleRate == 0) {
- CLog.e(
- "Failed to get a valid interval for even one metric collector. "
- + "Please make sure that the collectors have non-zero intervals "
- + "specified as an argument to this class.");
- return;
- }
-
- // TODO(b/70394486): Investigate if ScheduledThreadPool is better suited here so that we can
- // schedule all the metrics in their own thread and create a common object which allows
- // running of only one collector at a time.
- mTimer = new Timer();
-
- TimerTask timerTask =
- new TimerTask() {
- @Override
- public void run() {
- collect(runData);
- }
- };
-
- mTimer.scheduleAtFixedRate(timerTask, 0, mScheduleRate);
- }
-
- /**
- * Sets up the collection process by parsing all the args, retrieving the intervals from the
- * args and initializing the last update value of each of the collectors to current time.
- */
- private void setupCollection() {
- parseAllArgs();
- for (ScheduledDeviceMetricCollector singleMetricCollector :
- mMetricCollectorIntervals.keySet()) {
- mLastUpdate.put(singleMetricCollector, System.currentTimeMillis());
- }
-
- mScheduleRate = gcdOfIntervals();
- }
-
- /**
- * Runs all the requested collectors sequentially. Dumps the output in {@code
- * mmResultsDirectory/outputDirFormat} of the collector prefixed.
- *
- * @param runData holds the filename of the metrics collected for each collector.
- */
- private void collect(DeviceMetricData runData) {
- for (ScheduledDeviceMetricCollector singleMetricCollector :
- mMetricCollectorIntervals.keySet()) {
-
- Long elapsedTime = System.currentTimeMillis() - mLastUpdate.get(singleMetricCollector);
-
- Long taskInterval = mMetricCollectorIntervals.get(singleMetricCollector);
-
- if (elapsedTime >= taskInterval) {
- try {
- for (ITestDevice device : getDevices()) {
- singleMetricCollector.collect(device, runData);
- }
- mLastUpdate.put(singleMetricCollector, System.currentTimeMillis());
- } catch (InterruptedException e) {
- CLog.e("Exception during %s", singleMetricCollector.getClass());
- CLog.e(e);
- }
- }
- }
- }
-
- /** Parse all the intervals provided in the command line. */
- private void parseAllArgs() {
- for (ScheduledDeviceMetricCollector metricCollector : mMetricCollectors) {
- Long value = mIntervalMs.getOrDefault(metricCollector.getTag(), 0L);
-
- if (value > 0) {
- mMetricCollectorIntervals.put(metricCollector, value);
- } else if (value < 0) {
- throw new IllegalArgumentException(
- metricCollector.getClass() + " expects a non negative interval.");
- }
- }
- }
-
- /** Get the {@code scheduleRate} common to all tasks which is the gcd of all the intervals. */
- private Long gcdOfIntervals() {
- Collection<Long> intervals = mMetricCollectorIntervals.values();
- if (intervals.isEmpty()) {
- return 0L;
- }
- BigInteger gcdSoFar = new BigInteger(intervals.iterator().next().toString());
-
- for (Long interval : intervals) {
- gcdSoFar = gcdSoFar.gcd(new BigInteger(interval.toString()));
- }
-
- return gcdSoFar.longValue();
- }
-
- @Override
- public final void onTestRunEnd(
- DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) {
- if (mTimer != null) {
- mTimer.cancel();
- mTimer.purge();
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java
deleted file mode 100644
index 409ac2b..0000000
--- a/test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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.device.metric;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.util.FileUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * A {@link IMetricCollector} that allows to run a collection task periodically at a set interval.
- */
-public abstract class ScheduledDeviceMetricCollector extends BaseDeviceMetricCollector {
-
- @Option(
- name = "fixed-schedule-rate",
- description = "Schedule the timetask as a fixed schedule rate"
- )
- private boolean mFixedScheduleRate = false;
-
- @Option(
- name = "interval",
- description = "the interval between two tasks being scheduled",
- isTimeVal = true
- )
- private long mIntervalMs = 60 * 1000l;
-
- private Timer timer;
-
- @Override
- public final void onTestRunStart(DeviceMetricData runData) {
- CLog.d("starting with interval = %s", mIntervalMs);
- onStart(runData);
- timer = new Timer();
- TimerTask timerTask =
- new TimerTask() {
- @Override
- public void run() {
- try {
- for (ITestDevice device : getDevices()) {
- collect(device, runData);
- }
- } catch (InterruptedException e) {
- timer.cancel();
- Thread.currentThread().interrupt();
- CLog.e("Interrupted exception thrown from task:");
- CLog.e(e);
- }
- }
- };
-
- if (mFixedScheduleRate) {
- timer.scheduleAtFixedRate(timerTask, 0, mIntervalMs);
- } else {
- timer.schedule(timerTask, 0, mIntervalMs);
- }
- }
-
- @Override
- public final void onTestRunEnd(
- DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) {
- if (timer != null) {
- timer.cancel();
- timer.purge();
- }
- onEnd(runData);
- CLog.d("finished");
- }
-
- /**
- * Task periodically & asynchronously run during the test running on a specific device.
- *
- * @param device the {@link ITestDevice} the metric is associated to.
- * @param runData the {@link DeviceMetricData} where to put metrics.
- * @throws InterruptedException
- */
- abstract void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException;
-
- /**
- * Executed when entering this collector.
- *
- * @param runData the {@link DeviceMetricData} where to put metrics.
- */
- void onStart(DeviceMetricData runData) {
- // Does nothing.
- }
-
- /**
- * Executed when finishing this collector.
- *
- * @param runData the {@link DeviceMetricData} where to put metrics.
- */
- void onEnd(DeviceMetricData runData) {
- // Does nothing.
- }
-
- /**
- * Send all the output of a process from all the devices to a file.
- *
- * <p>Please note, metric collections should not overlap.
- *
- * @throws DeviceNotAvailableException
- * @throws IOException
- */
- File saveProcessOutput(ITestDevice device, String command, String outputFileName)
- throws DeviceNotAvailableException, IOException {
- String output = device.executeShellCommand(command);
-
- // Create the output file and dump the output of the command to this file.
- File outputFile = new File(outputFileName);
-
- FileUtil.writeToFile(output, outputFile);
-
- return outputFile;
- }
-
- /**
- * Create a suffix string to be appended at the end of each metric file to keep the name unique
- * at each run.
- *
- * @return suffix string in the format year-month-date-hour-minute-seconds-milliseconds.
- */
- @VisibleForTesting
- String getFileSuffix() {
- return new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US).format(new Date());
- }
-
- /**
- * Creates temporary directory to store the metric files.
- *
- * @return {@link File} directory with 'tmp' prefixed to its name to signify that its temporary.
- * @throws IOException
- */
- @VisibleForTesting
- File createTempDir() throws IOException {
- return FileUtil.createTempDir(String.format("tmp_%s", getTag()));
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/TemperatureCollector.java b/test_framework/com/android/tradefed/device/metric/TemperatureCollector.java
deleted file mode 100644
index 2c38cd5..0000000
--- a/test_framework/com/android/tradefed/device/metric/TemperatureCollector.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import static com.android.tradefed.targetprep.TemperatureThrottlingWaiter.DEVICE_TEMPERATURE_FILE_PATH_NAME;
-
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
-import com.android.tradefed.metrics.proto.MetricMeasurement.DoubleValues;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A {@link ScheduledDeviceMetricCollector} to measure min and max device temperature. Useful for
- * long duration performance tests to monitor if the device overheats.
- */
-public class TemperatureCollector extends ScheduledDeviceMetricCollector {
-
- private static final String CELCIUS_UNIT = "celcius";
-
- // Option name intentionally shared with TemperatureThrottlingWaiter
- @Option(
- name = DEVICE_TEMPERATURE_FILE_PATH_NAME,
- description =
- "Name of file that contains device temperature. "
- + "Example: /sys/class/hwmon/hwmon1/device/msm_therm"
- )
- private String mDeviceTemperatureFilePath = null;
-
- @Option(
- name = "device-temperature-file-regex",
- description =
- "Regex to parse temperature file. First group must be the temperature parsable"
- + "to Double. Default: Result:(\\d+) Raw:.*"
- )
- private String mDeviceTemperatureFileRegex = "Result:(\\d+) Raw:.*";
-
- /**
- * Stores the highest recorded temperature per device. Device will not be present in the map if
- * no valid temperature was recorded.
- */
- private Map<ITestDevice, Double> mMaxDeviceTemps;
-
- /**
- * Stores the lowest recorded temperature per device. Device will not be present in the map if
- * no valid temperature was recorded.
- */
- private Map<ITestDevice, Double> mMinDeviceTemps;
-
- // Example: Result:32 Raw:7e51
- private static Pattern mTemperatureRegex;
-
- private Map<ITestDevice, DoubleValues.Builder> mValues;
-
- @Override
- void onStart(DeviceMetricData runData) {
- mTemperatureRegex = Pattern.compile(mDeviceTemperatureFileRegex);
- mMaxDeviceTemps = new HashMap<>();
- mMinDeviceTemps = new HashMap<>();
- mValues = new HashMap<>();
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- if (mDeviceTemperatureFilePath == null) {
- return;
- }
- try {
- if (!device.isAdbRoot()) {
- return;
- }
- Double temp = getTemperature(device);
- if (temp == null) {
- return;
- }
- if (mValues.get(device) == null) {
- mValues.put(device, DoubleValues.newBuilder());
- }
- mValues.get(device).addDoubleValue(temp);
- mMaxDeviceTemps.putIfAbsent(device, temp);
- mMinDeviceTemps.putIfAbsent(device, temp);
- if (mMaxDeviceTemps.get(device) < temp) {
- mMaxDeviceTemps.put(device, temp);
- }
- if (mMinDeviceTemps.get(device) > temp) {
- mMinDeviceTemps.put(device, temp);
- }
- } catch (DeviceNotAvailableException e) {
- CLog.e(e);
- }
- }
-
- private Double getTemperature(ITestDevice device) throws DeviceNotAvailableException {
- String cmd = "cat " + mDeviceTemperatureFilePath;
- String result = device.executeShellCommand(cmd).trim();
- Matcher m = mTemperatureRegex.matcher(result);
- if (m.matches()) {
- return Double.parseDouble(m.group(1));
- }
- CLog.e("Error parsing temperature file output: " + result);
- return null;
- }
-
- @Override
- void onEnd(DeviceMetricData runData) {
- for (ITestDevice device : getDevices()) {
- DoubleValues.Builder values = mValues.get(device);
- if (values != null) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(
- Measurements.newBuilder().setDoubleValues(values.build()).build());
- metric.setUnit(CELCIUS_UNIT).setType(DataType.RAW);
- runData.addMetricForDevice(device, "temperature", metric);
- }
- // Report the max and min for compatibility
- Double maxTemp = mMaxDeviceTemps.get(device);
- if (maxTemp != null) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(Measurements.newBuilder().setSingleDouble(maxTemp).build());
- // Since we report some processed value report it as PROCESSED.
- metric.setUnit(CELCIUS_UNIT).setType(DataType.PROCESSED);
- runData.addMetricForDevice(device, "max_temperature", metric);
- }
- Double minTemp = mMinDeviceTemps.get(device);
- if (minTemp != null) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(Measurements.newBuilder().setSingleDouble(minTemp).build());
- // Since we report some processed value report it as PROCESSED.
- metric.setUnit(CELCIUS_UNIT).setType(DataType.PROCESSED);
- runData.addMetricForDevice(device, "min_temperature", metric);
- }
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java
deleted file mode 100644
index f1cc72f..0000000
--- a/test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect kernel debug trace at regular intervals. */
-public class TraceMetricCollector extends ScheduledDeviceMetricCollector {
- TraceMetricCollector() {
- setTag("trace");
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- CLog.i("Running trace collector...");
- String outputFileName = String.format("%s/trace-%s", createTempDir(), getFileSuffix());
- File outputFile =
- saveProcessOutput(
- device, "cat /sys/kernel/debug/tracing/trace", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.TEXT,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
index 618b6bc..cd80332 100644
--- a/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
+++ b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
@@ -344,7 +344,14 @@
for (Entry<FieldDescriptor, Object> entry : fields.entrySet()) {
if (!(entry.getValue() instanceof Message) && !(entry.getValue() instanceof List)) {
if (isNumeric(entry.getValue().toString())) {
- // Construct the metric if it is numeric value.
+ // Check if the current field has to be used as prefix for other fields
+ // and add it to the list of prefixes.
+ if (mPerfettoPrefixKeyFields.contains(entry.getKey().toString())) {
+ keyPrefixOtherFields.add(String.format("%s-%s",
+ entry.getKey().getName().toString(), entry.getValue().toString()));
+ continue;
+ }
+ // Otherwise treat this numeric field as metric.
if (mNumberPattern.matcher(entry.getValue().toString()).matches()) {
convertedMetrics.put(
entry.getKey().getName(),
@@ -355,9 +362,9 @@
convertedMetrics.put(
entry.getKey().getName(),
TfMetricProtoUtil.stringToMetric(
- Long.toString(
- Double.valueOf(entry.getValue().toString())
- .longValue()))
+ Long.toString(
+ Double.valueOf(entry.getValue().toString())
+ .longValue()))
.toBuilder());
}
} else {
@@ -375,20 +382,6 @@
}
}
- // Add prefix key to all the keys in current proto message which has numeric values.
- Map<String, Metric.Builder> additionalConvertedMetrics =
- new HashMap<String, Metric.Builder>();
- for (String prefix : keyPrefixOtherFields) {
- for (Map.Entry<String, Metric.Builder> currentMetric : convertedMetrics.entrySet()) {
- additionalConvertedMetrics.put(String.format("%s-%s", prefix,
- currentMetric.getKey()), currentMetric.getValue());
- }
- }
-
- // Not cleaning up the other metrics without prefix fields.
- convertedMetrics.putAll(additionalConvertedMetrics);
-
-
// Recursively expand the proto messages and repeated fields(i.e list).
// Recursion when there are no messages or list with in the current message.
for (Entry<FieldDescriptor, Object> entry : fields.entrySet()) {
@@ -458,6 +451,20 @@
}
}
}
+
+ // Add prefix key to all the keys in current proto message which has numeric values.
+ Map<String, Metric.Builder> additionalConvertedMetrics =
+ new HashMap<String, Metric.Builder>();
+ for (String prefix : keyPrefixOtherFields) {
+ for (Map.Entry<String, Metric.Builder> currentMetric : convertedMetrics.entrySet()) {
+ additionalConvertedMetrics.put(String.format("%s-%s", prefix,
+ currentMetric.getKey()), currentMetric.getValue());
+ }
+ }
+
+ // Not cleaning up the other metrics without prefix fields.
+ convertedMetrics.putAll(additionalConvertedMetrics);
+
return convertedMetrics;
}
@@ -511,3 +518,4 @@
return mProcessedMetric ? DataType.PROCESSED : DataType.RAW;
}
}
+
diff --git a/test_framework/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java b/test_framework/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java
index 977de89..7836678 100644
--- a/test_framework/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java
+++ b/test_framework/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java
@@ -89,7 +89,7 @@
throw new TargetSetupError(
"Failed to find a valid test zip directory.",
device.getDeviceDescriptor(),
- InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
if (mForceQueryable && device.isAppEnumerationSupported()) {
mInstallArgs.add("--force-queryable");
@@ -112,7 +112,7 @@
throw new TargetSetupError(
"Invalid test zip directory!",
device.getDeviceDescriptor(),
- InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
CLog.d("Installing all apks found in dir %s ...", directory.getAbsolutePath());
File[] files = directory.listFiles();
diff --git a/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java b/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java
new file mode 100644
index 0000000..0d32890
--- /dev/null
+++ b/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 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.targetprep;
+
+import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.command.remote.DeviceDescriptor;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipFile;
+
+/** Create chroot directory for ART tests. */
+@OptionClass(alias = "art-chroot-preparer")
+public class ArtChrootPreparer extends BaseTargetPreparer {
+
+ // Predefined location of the chroot root directory.
+ public static final String CHROOT_PATH = "/data/local/tmp/art-test-chroot";
+
+ // Directories to create in the chroot.
+ private static final String[] MKDIRS = {
+ "/", "/apex", "/data", "/data/dalvik-cache", "/data/local/tmp", "/tmp",
+ };
+
+ // System mount points to replicate in the chroot.
+ private static final String[] MOUNTS = {
+ "/dev", "/linkerconfig", "/proc", "/sys", "/system", "/apex/com.android.os.statsd",
+ };
+
+ @Override
+ public void setUp(TestInformation testInfo)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ ITestDevice device = testInfo.getDevice();
+
+ // Ensure there are no files left from previous runs.
+ cleanup(device);
+
+ // Create directories required for ART testing in chroot.
+ for (String dir : MKDIRS) {
+ adbShell(device, "mkdir -p " + CHROOT_PATH + dir);
+ }
+
+ // Replicate system mount point in the chroot.
+ for (String dir : MOUNTS) {
+ adbShell(device, "mkdir -p " + CHROOT_PATH + dir);
+ adbShell(device, "mount --bind " + dir + " " + CHROOT_PATH + dir);
+ }
+
+ // Activate APEXes in the chroot.
+ IBuildInfo buildInfo = testInfo.getBuildInfo();
+ IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo) buildInfo;
+ DeviceDescriptor deviceDesc = device.getDeviceDescriptor();
+ File tests_dir = deviceBuild.getFile(BuildInfoFileKey.TARGET_LINKED_DIR);
+ // The art_chroot is a shared module containing comment ART test data.
+ File apexes_dir = FileUtil.getFileForPath(tests_dir, "art_chroot", "system", "apex");
+ if (apexes_dir.listFiles() == null) {
+ throw new TargetSetupError(
+ "No apex files found in " + apexes_dir.getPath(), deviceDesc);
+ }
+ File tempDir = null;
+ try {
+ tempDir = FileUtil.createTempDir("art-test-apex");
+ for (File apex : apexes_dir.listFiles()) {
+ activateApex(device, tempDir, apex);
+ }
+ } catch (IOException e) {
+ throw new TargetSetupError("Error when activating apex", e, deviceDesc);
+ } finally {
+ FileUtil.recursiveDelete(tempDir);
+ }
+ }
+
+ private void activateApex(ITestDevice device, File tempDir, File apex)
+ throws TargetSetupError, IOException, DeviceNotAvailableException {
+ CLog.i("Activate apex in ART chroot: " + apex.getName());
+ ZipFile apex_zip = new ZipFile(apex);
+ ZipArchiveEntry apex_payload = apex_zip.getEntry("apex_payload.img");
+ File temp = FileUtil.createTempFile("payload-", ".img", tempDir);
+ FileUtil.writeToFile(apex_zip.getInputStream(apex_payload), temp);
+ String deviceApexDir = CHROOT_PATH + "/apex/" + apex.getName();
+ // Rename "com.android.art.testing.apex" to just "com.android.art.apex".
+ deviceApexDir = deviceApexDir.replace(".testing.apex", "").replace(".apex", "");
+ String deviceApexImg = deviceApexDir + ".img";
+ if (!device.pushFile(temp, deviceApexImg)) {
+ throw new TargetSetupError(
+ "adb push failed for " + apex.getName(), device.getDeviceDescriptor());
+ }
+ // TODO(b/168048638): Work-around for cuttlefish: first losetup call always fails.
+ device.executeShellV2Command("losetup -f");
+ // Mount the apex file via a loopback device.
+ String loopbackDevice = adbShell(device, "losetup -f -s " + deviceApexImg);
+ adbShell(device, "mkdir -p " + deviceApexDir);
+ adbShell(device, "mount -o loop,ro " + loopbackDevice + " " + deviceApexDir);
+ }
+
+ @Override
+ public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ try {
+ cleanup(testInfo.getDevice());
+ } catch (TargetSetupError ex) {
+ CLog.e("Tear-down failed: " + ex.toString());
+ }
+ }
+
+ // Wrapper for executeShellV2Command that checks that the command succeeds.
+ private String adbShell(ITestDevice device, String cmd)
+ throws TargetSetupError, DeviceNotAvailableException {
+ CommandResult result = device.executeShellV2Command(cmd);
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ throw new TargetSetupError(
+ String.format(
+ "adb shell command failed: '%s': %s".format(cmd, result.getStderr())));
+ }
+ return result.getStdout();
+ }
+
+ private void cleanup(ITestDevice device) throws TargetSetupError, DeviceNotAvailableException {
+ String mounts = adbShell(device, "mount");
+ Pattern pattern = Pattern.compile("^([^ ]+) on ([^ ]+) type ([^ ]+) .*$");
+ for (String mount : mounts.split("\n")) {
+ Matcher matcher = pattern.matcher(mount);
+ if (!matcher.matches()) {
+ throw new TargetSetupError("Failed to parse mount command output: " + mount);
+ }
+ if (matcher.group(2).startsWith(CHROOT_PATH)) {
+ adbShell(device, "umount " + matcher.group(2));
+ }
+ }
+ adbShell(device, "rm -rf " + CHROOT_PATH);
+ }
+}
diff --git a/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java b/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java
index ab335e7..d54cb49 100644
--- a/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java
@@ -26,11 +26,14 @@
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.SparseImageUtil;
import com.android.tradefed.util.ZipUtil;
import com.android.tradefed.util.ZipUtil2;
+
+import org.apache.commons.compress.archivers.zip.ZipFile;
+
import java.io.File;
import java.io.IOException;
-import org.apache.commons.compress.archivers.zip.ZipFile;
/**
* An {@link ITargetPreparer} that sets up a system image on top of a device build with the Dynamic
@@ -43,11 +46,15 @@
private static final String DEST_PATH = "/sdcard/system.raw.gz";
@Option(
- name = "system-image-zip-name",
- description = "The name of the zip file containing system.img."
- )
+ name = "system-image-zip-name",
+ description = "The name of the zip file containing system.img.")
private String mSystemImageZipName = "system-img.zip";
+ @Option(
+ name = "user-data-size-in-gb",
+ description = "Number of GB to be allocated for DSU user-data.")
+ private long mUserDataSizeInGb = 16L; // 16GB
+
private boolean isDSURunning(ITestDevice device) throws DeviceNotAvailableException {
CollectingOutputReceiver receiver = new CollectingOutputReceiver();
device.executeShellCommand("gsi_tool status", receiver);
@@ -62,20 +69,26 @@
throw new BuildError(
"Cannot find " + mSystemImageZipName + " in build info.",
device.getDeviceDescriptor(),
- InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
ZipFile zipFile = null;
File systemImage = null;
+ File rawSystemImage = null;
File systemImageGZ = null;
try {
zipFile = new ZipFile(systemImageZipFile);
systemImage = ZipUtil2.extractFileFromZip(zipFile, "system.img");
- // The prequest here is the system.img must be an unsparsed image.
- // Is there any way to detect the actual format and convert it accordingly.
+ if (SparseImageUtil.isSparse(systemImage)) {
+ rawSystemImage = FileUtil.createTempFile("system", ".raw");
+ SparseImageUtil.unsparse(systemImage, rawSystemImage);
+ } else {
+ // system.img is already non-sparse
+ rawSystemImage = systemImage;
+ }
systemImageGZ = FileUtil.createTempFile("system", ".raw.gz");
- long rawSize = systemImage.length();
- ZipUtil.gzipFile(systemImage, systemImageGZ);
+ long rawSize = rawSystemImage.length();
+ ZipUtil.gzipFile(rawSystemImage, systemImageGZ);
CLog.i("Pushing %s to %s", systemImageGZ.getAbsolutePath(), DEST_PATH);
if (!device.pushFile(systemImageGZ, DEST_PATH)) {
throw new TargetSetupError(
@@ -95,17 +108,26 @@
+ "--el KEY_SYSTEM_SIZE "
+ rawSize
+ " "
- + "--el KEY_USERDATA_SIZE 8589934592 "
- + "--ez KEY_ENABLE_WHEN_COMPLETED true";
+ + "--el KEY_USERDATA_SIZE "
+ + mUserDataSizeInGb * 1024 * 1024 * 1024
+ + " --ez KEY_ENABLE_WHEN_COMPLETED true";
device.executeShellCommand(command);
// Check if device shows as unavailable (as expected after the activity finished).
- device.waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000);
- device.waitForDeviceOnline();
- // the waitForDeviceOnline may block and we need to correct the 'i'
- // which is used to measure timeout accordingly
- if (!isDSURunning(device)) {
+ if (!device.waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000)) {
throw new TargetSetupError(
- "Timeout to boot into DSU", device.getDeviceDescriptor());
+ "Timed out waiting for DSU installation to complete and reboot",
+ device.getDeviceDescriptor());
+ }
+ try {
+ // waitForDeviceOnline() throws DeviceNotAvailableException if device does not
+ // become online within timeout.
+ device.waitForDeviceOnline();
+ } catch (DeviceNotAvailableException e) {
+ throw new TargetSetupError(
+ "Timed out booting into DSU", e, device.getDeviceDescriptor());
+ }
+ if (!isDSURunning(device)) {
+ throw new TargetSetupError("Failed to boot into DSU", device.getDeviceDescriptor());
}
CommandResult result = device.executeShellV2Command("gsi_tool enable");
if (CommandStatus.SUCCESS.equals(result.getStatus())) {
@@ -120,6 +142,7 @@
"fail to install the DynamicSystemUpdate", e, device.getDeviceDescriptor());
} finally {
FileUtil.deleteFile(systemImage);
+ FileUtil.deleteFile(rawSystemImage);
FileUtil.deleteFile(systemImageGZ);
ZipUtil2.closeZip(zipFile);
}
diff --git a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
index f3de9b4..0e61a42 100644
--- a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
@@ -29,6 +29,9 @@
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
+import com.android.tradefed.result.error.ErrorIdentifier;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
import com.android.tradefed.testtype.IInvocationContextReceiver;
@@ -137,9 +140,10 @@
* Helper method to only throw if mAbortOnFailure is enabled. Callers should behave as if this
* method may return.
*/
- private void fail(String message, DeviceDescriptor descriptor) throws TargetSetupError {
+ private void fail(String message, DeviceDescriptor descriptor, ErrorIdentifier identifier)
+ throws TargetSetupError {
if (shouldAbortOnFailure()) {
- throw new TargetSetupError(message, descriptor);
+ throw new TargetSetupError(message, descriptor, identifier);
} else {
// Log the error and return
Log.w(LOG_TAG, message);
@@ -153,7 +157,10 @@
for (String pushspec : mPushSpecs) {
String[] pair = pushspec.split("->");
if (pair.length != 2) {
- fail(String.format("Invalid pushspec: '%s'", Arrays.asList(pair)), descriptor);
+ fail(
+ String.format("Invalid pushspec: '%s'", Arrays.asList(pair)),
+ descriptor,
+ InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR);
continue;
}
remoteToLocalMapping.put(pair[1], new File(pair[0]));
@@ -311,6 +318,22 @@
// approach to do individual download from remote artifact.
// Try to stage the files from remote zip files.
src = buildInfo.stageRemoteFile(fileName, testDir);
+ if (src != null) {
+ try {
+ // Search again with filtering on ABI
+ File srcWithAbi = FileUtil.findFile(fileName, mAbi, testDir);
+ if (srcWithAbi != null
+ && !srcWithAbi
+ .getAbsolutePath()
+ .startsWith(src.getAbsolutePath())) {
+ // When multiple matches are found, return the one with matching
+ // ABI unless src is its parent directory.
+ return srcWithAbi;
+ }
+ } catch (IOException e) {
+ CLog.w("Failed to find test files with matching ABI from directory.");
+ }
+ }
}
}
return src;
@@ -388,7 +411,8 @@
if (src == null || !src.exists()) {
fail(
String.format("Local source file '%s' does not exist", localPath),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
return;
}
if (src.isDirectory()) {
@@ -402,7 +426,8 @@
String.format(
"Attempting to push dir '%s' to an existing device file '%s'",
src.getAbsolutePath(), remotePath),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.FAIL_PUSH_FILE);
}
Set<String> filter = new HashSet<>();
if (mAbi != null) {
@@ -415,7 +440,8 @@
fail(
String.format(
"Failed to push local '%s' to remote '%s'", localPath, remotePath),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.FAIL_PUSH_FILE);
return;
} else {
if (deleteContentOnly) {
@@ -428,7 +454,8 @@
fail(
String.format(
"Failed to push local '%s' to remote '%s'", localPath, remotePath),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.FAIL_PUSH_FILE);
return;
} else {
mFilesPushed.add(remotePath);
diff --git a/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java b/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
index 7034330..90928da 100644
--- a/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
@@ -26,6 +26,7 @@
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.PythonVirtualenvHelper;
import com.android.tradefed.util.RunUtil;
import java.io.File;
@@ -40,8 +41,7 @@
@OptionClass(alias = "python-venv")
public class PythonVirtualenvPreparer extends BaseTargetPreparer {
- private static final String PIP = "pip";
- private static final String PATH = "PATH";
+ private static final String PIP = "pip3";
protected static final String PYTHONPATH = "PYTHONPATH";
private static final int BASE_TIMEOUT = 1000 * 60;
@@ -70,6 +70,7 @@
protected void installDeps(IBuildInfo buildInfo, ITestDevice device) throws TargetSetupError {
boolean hasDependencies = false;
+ mPip = getPipPath();
if (mRequirementsFile != null) {
CommandResult c = mRunUtil.runTimedCmd(BASE_TIMEOUT * 5, mPip,
"install", "-r", mRequirementsFile.getAbsolutePath());
@@ -90,6 +91,9 @@
CLog.e("Installing %s failed", dep);
throw new TargetSetupError("Failed to install dependencies with pip",
device.getDeviceDescriptor());
+ } else {
+ CLog.d("Successfullly installed %s.", dep);
+ CLog.d("Stdout: %s", c.getStdout());
}
hasDependencies = true;
}
@@ -99,9 +103,12 @@
} else {
// make the install directory of new packages available to other classes that
// receive the build
- buildInfo.setFile(PYTHONPATH, new File(mVenvDir,
- "local/lib/python2.7/site-packages"),
+ // TODO(b/166688272): Get install location from pip rather than hard code it.
+ buildInfo.setFile(
+ PYTHONPATH,
+ new File(mVenvDir, "local/lib/python3.8/site-packages"),
buildInfo.getBuildId());
+ buildInfo.setFile("VIRTUAL_ENV", mVenvDir, buildInfo.getBuildId());
}
}
@@ -109,13 +116,26 @@
throws TargetSetupError {
if (mVenvDir != null) {
CLog.i("Using existing virtualenv based at %s", mVenvDir.getAbsolutePath());
- activate();
+ PythonVirtualenvHelper.activate(mRunUtil, mVenvDir);
return;
}
+ checkVirtualenvVersion(device);
try {
mVenvDir = FileUtil.createNamedTempDir(buildInfo.getTestTag() + "-virtualenv");
- mRunUtil.runTimedCmd(BASE_TIMEOUT, "virtualenv", mVenvDir.getAbsolutePath());
- activate();
+ CommandResult c =
+ mRunUtil.runTimedCmd(BASE_TIMEOUT, "virtualenv", mVenvDir.getAbsolutePath());
+ if (c.getStatus() != CommandStatus.SUCCESS) {
+ CLog.e("Creating virtual environment at %s failed.", mVenvDir.getAbsoluteFile());
+ CLog.e(
+ "Status: %s\nStdout: %s\nStderr: %s",
+ c.getStatus(), c.getStdout(), c.getStderr());
+ throw new TargetSetupError(
+ String.format(
+ "Failed to create virtual environment. Error:\n%s", c.getStderr()),
+ device.getDeviceDescriptor());
+ }
+ CLog.i("Created a virtualenv based at %s", mVenvDir.getAbsolutePath());
+ PythonVirtualenvHelper.activate(mRunUtil, mVenvDir);
} catch (IOException e) {
CLog.e("Failed to create temp directory for virtualenv");
throw new TargetSetupError("Error creating virtualenv", e,
@@ -131,13 +151,35 @@
mRequirementsFile = f;
}
- private void activate() {
- File binDir = new File(mVenvDir, "bin");
- mRunUtil.setWorkingDir(binDir);
- String path = System.getenv(PATH);
- mRunUtil.setEnvVariable(PATH, binDir + ":" + path);
- File pipFile = new File(binDir, PIP);
+ private String getPipPath() {
+ if (mVenvDir == null || !mVenvDir.exists()) {
+ return null;
+ }
+ String virtualenvPath = mVenvDir.getAbsolutePath();
+ File pipFile = new File(PythonVirtualenvHelper.getPythonBinDir(virtualenvPath), PIP);
pipFile.setExecutable(true);
- mPip = pipFile.getAbsolutePath();
+ return pipFile.getAbsolutePath();
+ }
+
+ /** Check if the virtualenv on the host is too old. */
+ private void checkVirtualenvVersion(ITestDevice device) throws TargetSetupError {
+ CommandResult result = mRunUtil.runTimedCmd(BASE_TIMEOUT, "virtualenv", "--version");
+ if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
+ throw new TargetSetupError(
+ "Failed to run `virtualenv --version`. Reason:\n" + result.getStderr(),
+ device.getDeviceDescriptor());
+ }
+ String stdout = result.getStdout(); // should start with 'virtualenv <version> from'
+ if (stdout.contains("command not found")) {
+ throw new TargetSetupError(
+ "virtualenv is not installed.", device.getDeviceDescriptor());
+ }
+ String version = stdout.split(" ")[1];
+ int majorVersion = Integer.parseInt(version.split("\\.")[0]);
+ if (majorVersion < 20) {
+ throw new TargetSetupError(
+ "virtualenv is too old. Required: >=20.0.1, yours: " + version,
+ device.getDeviceDescriptor());
+ }
}
}
\ No newline at end of file
diff --git a/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java
index e3c485c..fd4c694 100644
--- a/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java
@@ -16,9 +16,11 @@
package com.android.tradefed.targetprep;
+import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.IDeviceManager;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.ITestLogger;
@@ -86,6 +88,11 @@
@Option(name = "host-cmd-timeout", description = "Timeout for each command specified.")
private Duration mTimeout = Duration.ofMinutes(1L);
+ @Option(
+ name = "use-flashing-permit",
+ description = "Acquire a flashing permit before running commands.")
+ private boolean mUseFlashingPermit = false;
+
private List<Process> mBgProcesses = new ArrayList<>();
private List<BgCommandLog> mBgCommandLogs = new ArrayList<>();
private ITestLogger mLogger;
@@ -147,7 +154,16 @@
}
ITestDevice device = testInfo.getDevice();
replaceSerialNumber(mSetUpCommands, device);
- runCommandList(mSetUpCommands, device);
+ try {
+ if (mUseFlashingPermit) {
+ getDeviceManager().takeFlashingPermit();
+ }
+ runCommandList(mSetUpCommands, device);
+ } finally {
+ if (mUseFlashingPermit) {
+ getDeviceManager().returnFlashingPermit();
+ }
+ }
try {
mBgCommandLogs = createBgCommandLogs();
@@ -164,9 +180,16 @@
ITestDevice device = testInfo.getDevice();
replaceSerialNumber(mTearDownCommands, device);
try {
+ if (mUseFlashingPermit) {
+ getDeviceManager().takeFlashingPermit();
+ }
runCommandList(mTearDownCommands, device);
} catch (TargetSetupError tse) {
CLog.e(tse);
+ } finally {
+ if (mUseFlashingPermit) {
+ getDeviceManager().returnFlashingPermit();
+ }
}
// Terminate background commands after test finished
@@ -273,6 +296,12 @@
return mRunUtil;
}
+ /** @return {@link IDeviceManager} instance used for flashing permits */
+ @VisibleForTesting
+ IDeviceManager getDeviceManager() {
+ return GlobalConfiguration.getDeviceManagerInstance();
+ }
+
/**
* Create a BgCommandLog object that is based on a temporary file for each background command
*
diff --git a/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java
index def9252..6dd2b56 100644
--- a/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java
@@ -56,6 +56,11 @@
@Option(name = "script-timeout", description = "Script execution timeout.")
private Duration mTimeout = Duration.ofMinutes(1L);
+ @Option(
+ name = "use-flashing-permit",
+ description = "Acquire a flashing permit before executing the script.")
+ private boolean mUseFlashingPermit = false;
+
private IRunUtil mRunUtil;
@Override
@@ -82,28 +87,15 @@
getRunUtil().setEnvVariable("ANDROID_SERIAL", device.getSerialNumber());
setPathVariable(testInfo);
- // Execute script and handle result
- CommandResult result =
- getRunUtil().runTimedCmd(mTimeout.toMillis(), scriptFile.getAbsolutePath());
- switch (result.getStatus()) {
- case SUCCESS:
- CLog.i("Script executed successfully, stdout = [%s].", result.getStdout());
- break;
- case FAILED:
- throw new TargetSetupError(
- String.format(
- "Script execution failed, stdout = [%s], stderr = [%s].",
- result.getStdout(), result.getStderr()),
- device.getDeviceDescriptor());
- case TIMED_OUT:
- throw new TargetSetupError(
- "Script execution timed out.", device.getDeviceDescriptor());
- case EXCEPTION:
- throw new TargetSetupError(
- String.format(
- "Exception during script execution, stdout = [%s], stderr = [%s].",
- result.getStdout(), result.getStderr()),
- device.getDeviceDescriptor());
+ try {
+ if (mUseFlashingPermit) {
+ getDeviceManager().takeFlashingPermit();
+ }
+ executeScript(scriptFile, device);
+ } finally {
+ if (mUseFlashingPermit) {
+ getDeviceManager().returnFlashingPermit();
+ }
}
}
@@ -116,7 +108,7 @@
return mRunUtil;
}
- /** @return {@link IDeviceManager} instance used to fetch the configured adb/fastboot paths */
+ /** @return {@link IDeviceManager} instance used for adb/fastboot paths and flashing permits */
@VisibleForTesting
IDeviceManager getDeviceManager() {
return GlobalConfiguration.getDeviceManagerInstance();
@@ -175,4 +167,35 @@
getRunUtil().setEnvVariable("PATH", path);
}
}
+
+ /**
+ * Execute script and handle result.
+ *
+ * @param scriptFile script file to execute
+ * @param device device being prepared
+ */
+ private void executeScript(File scriptFile, ITestDevice device) throws TargetSetupError {
+ CommandResult result =
+ getRunUtil().runTimedCmd(mTimeout.toMillis(), scriptFile.getAbsolutePath());
+ switch (result.getStatus()) {
+ case SUCCESS:
+ CLog.i("Script executed successfully, stdout = [%s].", result.getStdout());
+ break;
+ case FAILED:
+ throw new TargetSetupError(
+ String.format(
+ "Script execution failed, stdout = [%s], stderr = [%s].",
+ result.getStdout(), result.getStderr()),
+ device.getDeviceDescriptor());
+ case TIMED_OUT:
+ throw new TargetSetupError(
+ "Script execution timed out.", device.getDeviceDescriptor());
+ case EXCEPTION:
+ throw new TargetSetupError(
+ String.format(
+ "Exception during script execution, stdout = [%s], stderr = [%s].",
+ result.getStdout(), result.getStderr()),
+ device.getDeviceDescriptor());
+ }
+ }
}
diff --git a/test_framework/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java
new file mode 100644
index 0000000..7f2b28d
--- /dev/null
+++ b/test_framework/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 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.targetprep;
+
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.UserInfo;
+import com.android.tradefed.invoker.TestInformation;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An {@link ITargetPreparer} that creates a secondary user in setup, and marks that tests should be
+ * run in that user.
+ *
+ * <p>In teardown, the secondary user is removed.
+ */
+@OptionClass(alias = "run-on-secondary-user")
+public class RunOnSecondaryUserTargetPreparer extends BaseTargetPreparer {
+
+ @VisibleForTesting static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER";
+
+ @VisibleForTesting static final String TEST_PACKAGE_NAME_OPTION = "test-package-name";
+
+ @Option(
+ name = TEST_PACKAGE_NAME_OPTION,
+ description =
+ "the name of a package to be installed on the secondary user. "
+ + "This must already be installed on the device.",
+ importance = Option.Importance.IF_UNSET)
+ private List<String> mTestPackages = new ArrayList<>();
+
+ @Override
+ public void setUp(TestInformation testInfo)
+ throws TargetSetupError, DeviceNotAvailableException {
+ int secondaryUserId = getSecondaryUserId(testInfo.getDevice());
+
+ if (secondaryUserId != -1) {
+ // There is already a secondary user - so we don't want to remove it
+ setDisableTearDown(true);
+ } else {
+ secondaryUserId = createSecondaryUser(testInfo.getDevice());
+ }
+
+ for (String pkg : mTestPackages) {
+ testInfo.getDevice()
+ .executeShellCommand(
+ "pm install-existing --user " + secondaryUserId + " " + pkg);
+ }
+
+ testInfo.properties().put(RUN_TESTS_AS_USER_KEY, Integer.toString(secondaryUserId));
+ }
+
+ /** Get the id of a secondary user currently on the device. -1 if there is none */
+ private static int getSecondaryUserId(ITestDevice device) throws DeviceNotAvailableException {
+ for (Map.Entry<Integer, UserInfo> userInfo : device.getUserInfos().entrySet()) {
+ if (userInfo.getValue().isSecondary()) {
+ return userInfo.getKey();
+ }
+ }
+ return -1;
+ }
+
+ /** Creates a secondary user and returns the new user ID. */
+ private static int createSecondaryUser(ITestDevice device) throws DeviceNotAvailableException {
+ final String createUserOutput = device.executeShellCommand("pm create-user secondary");
+ final int userId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
+ device.executeShellCommand("am start-user -w " + userId);
+ return userId;
+ }
+
+ @Override
+ public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ int userId = Integer.parseInt(testInfo.properties().get(RUN_TESTS_AS_USER_KEY));
+
+ testInfo.getDevice().removeUser(userId);
+ }
+}
diff --git a/test_framework/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java
new file mode 100644
index 0000000..274a096
--- /dev/null
+++ b/test_framework/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 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.targetprep;
+
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.UserInfo;
+import com.android.tradefed.invoker.TestInformation;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An {@link ITargetPreparer} that creates a work profile in setup, and marks that tests should be
+ * run in that user.
+ *
+ * <p>In teardown, the work profile is removed.
+ */
+@OptionClass(alias = "run-on-work-profile")
+public class RunOnWorkProfileTargetPreparer extends BaseTargetPreparer {
+
+ @VisibleForTesting static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER";
+
+ @VisibleForTesting static final String TEST_PACKAGE_NAME_OPTION = "test-package-name";
+
+ @Option(
+ name = TEST_PACKAGE_NAME_OPTION,
+ description =
+ "the name of a package to be installed on the work profile. "
+ + "This must already be installed on the device.",
+ importance = Option.Importance.IF_UNSET)
+ private List<String> mTestPackages = new ArrayList<>();
+
+ @Override
+ public void setUp(TestInformation testInfo)
+ throws TargetSetupError, DeviceNotAvailableException {
+ int workProfileId = getWorkProfileId(testInfo.getDevice());
+
+ if (workProfileId != -1) {
+ // There is already a work profile - so we don't want to remove it
+ setDisableTearDown(true);
+ } else {
+ workProfileId = createWorkProfile(testInfo.getDevice());
+ }
+
+ for (String pkg : mTestPackages) {
+ testInfo.getDevice()
+ .executeShellCommand("pm install-existing --user " + workProfileId + " " + pkg);
+ }
+
+ testInfo.properties().put(RUN_TESTS_AS_USER_KEY, Integer.toString(workProfileId));
+ }
+
+ /** Get the id of a work profile currently on the device. -1 if there is none */
+ private static int getWorkProfileId(ITestDevice device) throws DeviceNotAvailableException {
+ for (Map.Entry<Integer, UserInfo> userInfo : device.getUserInfos().entrySet()) {
+ if (userInfo.getValue().isManagedProfile()) {
+ return userInfo.getKey();
+ }
+ }
+ return -1;
+ }
+
+ /** Creates a work profile and returns the new user ID. */
+ private static int createWorkProfile(ITestDevice device) throws DeviceNotAvailableException {
+ int parentProfile = device.getCurrentUser();
+ final String createUserOutput =
+ device.executeShellCommand(
+ "pm create-user --profileOf " + parentProfile + " --managed work");
+ final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
+ device.executeShellCommand("am start-user -w " + profileId);
+ return profileId;
+ }
+
+ @Override
+ public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ int workProfileId = Integer.parseInt(testInfo.properties().get(RUN_TESTS_AS_USER_KEY));
+
+ testInfo.getDevice().removeUser(workProfileId);
+ }
+}
diff --git a/test_framework/com/android/tradefed/targetprep/WifiPreparer.java b/test_framework/com/android/tradefed/targetprep/WifiPreparer.java
index 1bfc635..b1f14e0 100644
--- a/test_framework/com/android/tradefed/targetprep/WifiPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/WifiPreparer.java
@@ -64,10 +64,14 @@
if (mVerifyOnly) {
if (!device.isWifiEnabled()) {
throw new TargetSetupError(
- "The device does not have wifi enabled.", device.getDeviceDescriptor());
+ "The device does not have wifi enabled.",
+ device.getDeviceDescriptor(),
+ InfraErrorIdentifier.NO_WIFI);
} else if (!device.checkConnectivity()) {
throw new TargetSetupError(
- "The device has no wifi connection.", device.getDeviceDescriptor());
+ "The device has no wifi connection.",
+ device.getDeviceDescriptor(),
+ InfraErrorIdentifier.NO_WIFI);
}
return;
}
diff --git a/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java b/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
index c22324d..cec9d30 100644
--- a/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
@@ -96,15 +96,14 @@
private Set<String> mSystemFileNames = new TreeSet<>();
@Option(
- name = "dummy-file-name",
- description =
- "the name of the image file to be replaced with a small dummy file. "
- + "Can be repeated. This option is used when the generic system "
- + "image is too large for the device's dynamic partition. "
- + "As GSI doesn't use product partition, the product image can be "
- + "replaced with a dummy file so as to free up space for GSI."
- )
- private Set<String> mDummyFileNames = new TreeSet<>();
+ name = "stub-file-name",
+ description =
+ "the name of the image file to be replaced with a small stub file. "
+ + "Can be repeated. This option is used when the generic system "
+ + "image is too large for the device's dynamic partition. "
+ + "As GSI doesn't use product partition, the product image can be "
+ + "replaced with a stub file so as to free up space for GSI.")
+ private Set<String> mStubFileNames = new TreeSet<>();
@Option(
name = "compression-level",
@@ -218,21 +217,21 @@
systemFiles = replaceExistingEntries(systemFiles, files);
filesNotInDeviceBuild.putAll(systemFiles);
- // Generate specified dummy files and replace those in device build.
- Map<String, InputStreamFactory> dummyFiles =
- createDummyInputStreamFactories(mDummyFileNames);
- Map<String, InputStreamFactory> dummyFilesNotInDeviceBuild =
- replaceExistingEntries(dummyFiles, files);
- // The purpose of the dummy files is to make fastboot shrink product partition.
- // Some devices don't have product partition and image. If the dummy file names are not
+ // Generate specified stub files and replace those in device build.
+ Map<String, InputStreamFactory> stubFiles =
+ createStubInputStreamFactories(mStubFileNames);
+ Map<String, InputStreamFactory> stubFilesNotInDeviceBuild =
+ replaceExistingEntries(stubFiles, files);
+ // The purpose of the stub files is to make fastboot shrink product partition.
+ // Some devices don't have product partition and image. If the stub file names are not
// found in device build, they are ignored so that devices with and without product
// partition can share configurations.
- // This preparer does not generate dummy files in super image because
+ // This preparer does not generate stub files in super image because
// build_super_image cannot handle unformatted files.
- if (!dummyFilesNotInDeviceBuild.isEmpty()) {
+ if (!stubFilesNotInDeviceBuild.isEmpty()) {
CLog.w(
- "Skip creating dummy images: %s",
- String.join(",", dummyFilesNotInDeviceBuild.keySet()));
+ "Skip creating stub images: %s",
+ String.join(",", stubFilesNotInDeviceBuild.keySet()));
}
if (resourceBuildInfo != null) {
@@ -254,7 +253,7 @@
throw new BuildError(
"Cannot get " + MISC_INFO_FILE_NAME + " from device build.",
device.getDeviceDescriptor(),
- InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
File otaToolsZip = mOtaToolsZip;
@@ -265,7 +264,7 @@
throw new BuildError(
"Cannot get " + OTATOOLS_ZIP_NAME + " from system build.",
systemNullDevice.getDeviceDescriptor(),
- InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
File repackSuperImageFile = mRepackSuperImageFile;
@@ -276,7 +275,7 @@
throw new BuildError(
"Cannot get " + REPACK_SUPER_IMAGE_FILE_NAME + " from system build.",
systemNullDevice.getDeviceDescriptor(),
- InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
mixedSuperImage = FileUtil.createTempFile("super", ".img");
@@ -376,14 +375,14 @@
return factories;
}
- private static Map<String, InputStreamFactory> createDummyInputStreamFactories(
- Collection<String> dummyFileNames) {
+ private static Map<String, InputStreamFactory> createStubInputStreamFactories(
+ Collection<String> stubFileNames) {
// The image size must be larger than zero. Otherwise fastboot cannot flash it.
byte[] data = new byte[] {0};
Map<String, InputStreamFactory> factories = new HashMap<>();
- for (String dummyFileName : dummyFileNames) {
+ for (String stubFileName : stubFileNames) {
factories.put(
- dummyFileName,
+ stubFileName,
new InputStreamFactory() {
@Override
public InputStream createInputStream() throws IOException {
@@ -598,8 +597,8 @@
}
@VisibleForTesting
- void addDummyFileName(String fileName) {
- mDummyFileNames.add(fileName);
+ void addStubFileName(String fileName) {
+ mStubFileNames.add(fileName);
}
@VisibleForTesting
diff --git a/test_framework/com/android/tradefed/testtype/ArtGTest.java b/test_framework/com/android/tradefed/testtype/ArtGTest.java
new file mode 100644
index 0000000..cf6a333
--- /dev/null
+++ b/test_framework/com/android/tradefed/testtype/ArtGTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 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;
+
+import com.android.tradefed.targetprep.ArtChrootPreparer;
+
+public class ArtGTest extends GTest {
+ @Override
+ protected String getGTestCmdLineWrapper(String fullPath, String flags) {
+ String chroot = ArtChrootPreparer.CHROOT_PATH;
+ if (fullPath.startsWith(chroot)) {
+ fullPath = fullPath.substring(chroot.length());
+ }
+ return String.format("chroot %s %s %s", chroot, fullPath, flags);
+ }
+}
diff --git a/test_framework/com/android/tradefed/testtype/ArtRunTest.java b/test_framework/com/android/tradefed/testtype/ArtRunTest.java
index a982104..e0dad70 100644
--- a/test_framework/com/android/tradefed/testtype/ArtRunTest.java
+++ b/test_framework/com/android/tradefed/testtype/ArtRunTest.java
@@ -17,35 +17,47 @@
package com.android.tradefed.testtype;
import com.android.ddmlib.CollectingOutputReceiver;
-
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
+import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.FileUtil;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
/** A test runner to run ART run-tests. */
-public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver {
+public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver, ITestFilterReceiver {
private static final String RUNTEST_TAG = "ArtRunTest";
private static final String DALVIKVM_CMD =
"dalvikvm|#BITNESS#| -classpath |#CLASSPATH#| |#MAINCLASS#|";
+ public static final String CHECKER_EXECUTABLE = "art/tools/checker/checker.py";
@Option(
name = "test-timeout",
@@ -63,6 +75,8 @@
private ITestDevice mDevice = null;
private IAbi mAbi = null;
+ private Set<String> mIncludeFilters = new LinkedHashSet<>();
+ private Set<String> mExcludeFilters = new LinkedHashSet<>();
/** {@inheritDoc} */
@Override
@@ -89,6 +103,54 @@
/** {@inheritDoc} */
@Override
+ public void addIncludeFilter(String filter) {
+ mIncludeFilters.add(filter);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void addAllIncludeFilters(Set<String> filters) {
+ mIncludeFilters.addAll(filters);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void addExcludeFilter(String filter) {
+ mExcludeFilters.add(filter);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void addAllExcludeFilters(Set<String> filters) {
+ mExcludeFilters.addAll(filters);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Set<String> getIncludeFilters() {
+ return mIncludeFilters;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Set<String> getExcludeFilters() {
+ return mExcludeFilters;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearIncludeFilters() {
+ mIncludeFilters.clear();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearExcludeFilters() {
+ mExcludeFilters.clear();
+ }
+
+ /** {@inheritDoc} */
+ @Override
public void run(TestInformation testInfo, ITestInvocationListener listener)
throws DeviceNotAvailableException {
if (mDevice == null) {
@@ -111,24 +173,29 @@
* Run a single ART run-test (on device).
*
* @param listener {@link ITestInvocationListener} listener for test
- * @throws DeviceNotAvailableException
+ * @throws DeviceNotAvailableException If there was a problem communicating with
+ * the test device.
*/
void runArtTest(TestInformation testInfo, ITestInvocationListener listener)
throws DeviceNotAvailableException {
+ String abi = mAbi.getName();
+ String runName = String.format("%s_%s", RUNTEST_TAG, abi);
+ TestDescription testId = new TestDescription(runName, mRunTestName);
+ if (shouldSkipCurrentTest(testId)) {
+ return;
+ }
+
CLog.i("Running ArtRunTest %s on %s", mRunTestName, mDevice.getSerialNumber());
String cmd = DALVIKVM_CMD;
- String abi = mAbi.getName();
cmd = cmd.replace("|#BITNESS#|", AbiUtils.getBitness(abi));
cmd = cmd.replace("|#CLASSPATH#|", ArrayUtil.join(File.pathSeparator, mClasspath));
// TODO: Turn this into an an option of the `ArtRunTest` class?
cmd = cmd.replace("|#MAINCLASS#|", "Main");
CLog.d("About to run run-test command: %s", cmd);
- String runName = String.format("%s_%s", RUNTEST_TAG, abi);
// Note: We only run one test at the moment.
int testCount = 1;
- TestDescription testId = new TestDescription(runName, mRunTestName);
listener.testRunStarted(runName, testCount);
listener.testStarted(testId);
@@ -143,7 +210,9 @@
// Check the output producted by the test.
if (output != null) {
try {
- File expectedFile = getDependencyFileFromRunTestDir(testInfo, "expected.txt");
+ String expectedFileName = String.format("%s-expected.txt", mRunTestName);
+ File expectedFile =
+ testInfo.getDependencyFile(expectedFileName, /* targetFirst */ true);
CLog.i("Found expected output for run-test %s: %s", mRunTestName, expectedFile);
String expected = FileUtil.readStringFromFile(expectedFile);
if (!output.equals(expected)) {
@@ -153,47 +222,145 @@
// current ART run-test scripts).
CLog.i("%s FAILED: %s", mRunTestName, error);
listener.testFailed(testId, error);
+ return;
}
} catch (IOException ioe) {
CLog.e(
"I/O error while accessing expected output file for test %s: %s",
mRunTestName, ioe);
listener.testFailed(testId, "I/O error while accessing expected output file.");
+ return;
}
} else {
listener.testFailed(testId, "No output received to compare to.");
+ return;
+ }
+
+ if (mRunTestName.contains("-checker-")) {
+ // not particularly reliable way of constructing a temporary dir
+ String cfgPathDir =
+ String.format("/data/local/tmp/%s", mRunTestName.replaceAll("/", "-"));
+ mDevice.executeShellCommand(String.format("mkdir -p \"%s\"", cfgPathDir));
+
+ String cfgPath = cfgPathDir + "/graph.cfg";
+ mDevice.executeShellCommand(
+ String.format(
+ "dex2oat --dex-file=%s --oat-file=/dev/null --dump-cfg=%s -j1",
+ mClasspath.get(0), cfgPath));
+
+ File runTestDir;
+ try {
+ runTestDir = getRunTestDir(testInfo);
+ } catch (FileNotFoundException e) {
+ listener.testFailed(testId, "I/O error while accessing test dir.");
+ return;
+ }
+
+ File localCfgPath = new File(runTestDir, "graph.cfg");
+ if (localCfgPath.isFile()) {
+ localCfgPath.delete();
+ }
+
+ mDevice.pullFile(cfgPath, localCfgPath);
+
+ File tempJar = new File(runTestDir, "temp.jar");
+ mDevice.pullFile(mClasspath.get(0), tempJar);
+
+ try (ZipFile archive = new ZipFile(tempJar)) {
+ File srcFile = new File(runTestDir, "src");
+ if (srcFile.exists()) {
+ Files.walk(srcFile.toPath())
+ .map(Path::toFile)
+ .sorted(Comparator.reverseOrder())
+ .forEach(File::delete);
+ }
+
+ List<? extends ZipEntry> entries = archive.stream()
+ .sorted(Comparator.comparing(ZipEntry::getName))
+ .collect(Collectors.toList());
+
+ for (ZipEntry entry : entries) {
+ if (entry.getName().startsWith("src")) {
+ Path entryDest = runTestDir.toPath().resolve(entry.getName());
+ if (entry.isDirectory()) {
+ Files.createDirectory(entryDest);
+ } else {
+ Files.copy(archive.getInputStream(entry), entryDest);
+ }
+ }
+ }
+ } catch (IOException e) {
+ listener.testFailed(testId, "Error unpacking test jar");
+ CLog.e("Jar unpacking failed with exception %s", e);
+ CLog.e(e);
+ return;
+ }
+
+ String checkerArch = AbiUtils.getArchForAbi(abi).toUpperCase();
+
+ ProcessBuilder processBuilder =
+ new ProcessBuilder(
+ CHECKER_EXECUTABLE,
+ "-q",
+ "--arch=" + checkerArch,
+ localCfgPath.getAbsolutePath(),
+ runTestDir.getAbsolutePath());
+
+ try {
+ Process process = processBuilder.start();
+ if (process.waitFor() != 0) {
+ String checkerOutput = new BufferedReader(
+ new InputStreamReader(process.getErrorStream())).lines().collect(
+ Collectors.joining("\n"));
+ listener.testFailed(testId, "Checker failed\n" + checkerOutput);
+ listener.testLog("graph.cfg", LogDataType.CFG,
+ new FileInputStreamSource(localCfgPath));
+ }
+ } catch (IOException | InterruptedException e) {
+ listener.testFailed(testId, "I/O error while starting Checker process");
+ }
}
} finally {
- HashMap<String, Metric> emptyTestMetrics = new HashMap();
+ HashMap<String, Metric> emptyTestMetrics = new HashMap<>();
listener.testEnded(testId, emptyTestMetrics);
- HashMap<String, Metric> emptyTestRunMetrics = new HashMap();
+ HashMap<String, Metric> emptyTestRunMetrics = new HashMap<>();
// TODO: Pass an actual value as `elapsedTimeMillis` argument.
listener.testRunEnded(/* elapsedTimeMillis*/ 0, emptyTestRunMetrics);
}
}
+ /**
+ * Check if current test should be skipped.
+ *
+ * @param description The test in progress.
+ * @return true if the test should be skipped.
+ */
+ private boolean shouldSkipCurrentTest(TestDescription description) {
+ // Force to skip any test not listed in include filters, or listed in exclude filters.
+ // exclude filters have highest priority.
+ String testName = description.getTestName();
+ String descString = description.toString();
+ if (mExcludeFilters.contains(testName) || mExcludeFilters.contains(descString)) {
+ return true;
+ }
+ if (!mIncludeFilters.isEmpty()) {
+ return !mIncludeFilters.contains(testName) && !mIncludeFilters.contains(descString);
+ }
+ return false;
+ }
+
/** Create an output receiver for the test command executed on the device. */
protected CollectingOutputReceiver createTestOutputReceiver() {
return new CollectingOutputReceiver();
}
- /** Search for a dependency/artifact file in the run-test's directory. */
- protected File getDependencyFileFromRunTestDir(TestInformation testInfo, String fileName)
- throws FileNotFoundException {
+ private File getRunTestDir(TestInformation testInfo) throws FileNotFoundException {
File testsDir = testInfo.executionFiles().get(FilesKey.TARGET_TESTS_DIRECTORY);
if (testsDir == null || !testsDir.exists()) {
throw new FileNotFoundException(
String.format(
"Could not find target tests directory for test %s.", mRunTestName));
}
- File runTestDir = new File(testsDir, mRunTestName);
- File file = FileUtil.findFile(runTestDir, fileName);
- if (file == null) {
- throw new FileNotFoundException(
- String.format(
- "Could not find an artifact file associated with %s in directory %s.",
- fileName, runTestDir));
- }
- return file;
+ return new File(testsDir, mRunTestName);
}
}
diff --git a/test_framework/com/android/tradefed/testtype/GTest.java b/test_framework/com/android/tradefed/testtype/GTest.java
index 7735e33..eb920e9 100644
--- a/test_framework/com/android/tradefed/testtype/GTest.java
+++ b/test_framework/com/android/tradefed/testtype/GTest.java
@@ -16,11 +16,6 @@
package com.android.tradefed.testtype;
-import static com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain.CLANG;
-import static com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain.GCOV;
-
-import static com.google.common.base.Verify.verify;
-
import com.android.ddmlib.FileListingService;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.tradefed.config.Option;
@@ -31,10 +26,8 @@
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.testtype.coverage.CoverageOptions;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.FileUtil;
-import com.android.tradefed.util.NativeCodeCoverageFlusher;
import com.google.common.annotations.VisibleForTesting;
@@ -156,6 +149,10 @@
return testPath.toString();
}
+ public void setNativeTestDevicePath(String path) {
+ mNativeTestDevicePath = path;
+ }
+
/**
* Executes all native tests in a folder as well as in all subfolders recursively.
*
@@ -427,26 +424,10 @@
if (mStopRuntime) {
mDevice.executeShellCommand("stop");
}
- // Insert the coverage listener if code coverage collection is enabled.
- listener = addNativeCoverageListenerIfEnabled(listener);
- listener = addClangCoverageListenerIfEnabled(listener);
listener = getGTestListener(listener);
- NativeCodeCoverageFlusher flusher =
- new NativeCodeCoverageFlusher(mDevice, getCoverageOptions().getCoverageProcesses());
Throwable throwable = null;
try {
- if (getCoverageOptions().isCoverageEnabled()) {
- // Enable abd root on the device, otherwise the following commands will fail.
- verify(mDevice.enableAdbRoot(), "Failed to enable adb root.");
-
- flusher.resetCoverage();
-
- // Clang will no longer create directories that are part of the GCOV_PREFIX
- // environment variable. Force create the /data/misc/trace/testcoverage dir to
- // prevent "No such file or directory" errors when writing test coverage to disk.
- mDevice.executeShellCommand("mkdir /data/misc/trace/testcoverage");
- }
doRunAllTestsInSubdirectory(testPath, mDevice, listener);
} catch (Throwable t) {
throwable = t;
@@ -460,41 +441,4 @@
}
}
}
-
- /**
- * Adds a listener to pull native code coverage measurements from the device after the test is
- * complete if coverage is enabled, otherwise returns the same listener.
- *
- * @param listener the current chain of listeners
- * @return a native coverage listener if coverage is enabled, otherwise the original listener
- */
- private ITestInvocationListener addNativeCoverageListenerIfEnabled(
- ITestInvocationListener listener) {
- CoverageOptions options = getCoverageOptions();
-
- if (options.isCoverageEnabled() && options.getCoverageToolchains().contains(GCOV)) {
- return new NativeCodeCoverageListener(mDevice, options, listener);
- }
- return listener;
- }
-
- /**
- * Adds a listener to pull Clang code coverage measurements from the device after the test is
- * complete if coverage is enabled, otherwise returns the same listener.
- *
- * @param listener the current chain of listeners
- * @return a native coverage listener if coverage is enabled, otherwise the original listener
- */
- private ITestInvocationListener addClangCoverageListenerIfEnabled(
- ITestInvocationListener listener) {
- CoverageOptions options = getCoverageOptions();
-
- if (options.isCoverageEnabled() && options.getCoverageToolchains().contains(CLANG)) {
- ClangCodeCoverageListener clangListener =
- new ClangCodeCoverageListener(mDevice, listener);
- clangListener.setConfiguration(getConfiguration());
- return clangListener;
- }
- return listener;
- }
}
diff --git a/test_framework/com/android/tradefed/testtype/GTestBase.java b/test_framework/com/android/tradefed/testtype/GTestBase.java
index 3ee7179..5b22e66 100644
--- a/test_framework/com/android/tradefed/testtype/GTestBase.java
+++ b/test_framework/com/android/tradefed/testtype/GTestBase.java
@@ -17,6 +17,7 @@
package com.android.tradefed.testtype;
import com.android.ddmlib.IShellOutputReceiver;
+import com.android.tradefed.config.Configuration;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.config.Option;
@@ -24,7 +25,6 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.testtype.coverage.CoverageOptions;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.FileUtil;
@@ -547,6 +547,14 @@
}
/**
+ * Helper which allows derived classes to wrap the gtest command under some other tool (chroot,
+ * strace, gdb, and similar).
+ */
+ protected String getGTestCmdLineWrapper(String fullPath, String flags) {
+ return String.format("%s %s", fullPath, flags);
+ }
+
+ /**
* Helper method to build the gtest command to run.
*
* @param fullPath absolute file system path to gtest binary on device
@@ -559,16 +567,12 @@
gTestCmdLine.append(String.format("LD_LIBRARY_PATH=%s ", mLdLibraryPath));
}
- if (getCoverageOptions().isCoverageEnabled()) {
- gTestCmdLine.append("GCOV_PREFIX=/data/misc/trace/testcoverage ");
- }
-
// su to requested user
if (mRunTestAs != null) {
gTestCmdLine.append(String.format("su %s ", mRunTestAs));
}
- gTestCmdLine.append(String.format("%s %s", fullPath, flags));
+ gTestCmdLine.append(getGTestCmdLineWrapper(fullPath, flags));
return gTestCmdLine.toString();
}
@@ -665,18 +669,10 @@
* @return an IConfiguration
*/
protected IConfiguration getConfiguration() {
- return mConfiguration;
- }
-
- /**
- * Returns the {@link CoverageOptions} for this test, if it exists. Otherwise returns a default
- * {@link CoverageOptions} object with all coverage disabled.
- */
- protected CoverageOptions getCoverageOptions() {
- if (mConfiguration != null) {
- return mConfiguration.getCoverageOptions();
+ if (mConfiguration == null) {
+ return new Configuration("", "");
}
- return new CoverageOptions();
+ return mConfiguration;
}
/**
diff --git a/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
index 373844a..837ba81 100644
--- a/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
+++ b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
@@ -26,6 +26,7 @@
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
+import com.android.tradefed.util.StringEscapeUtils;
import com.google.common.annotations.VisibleForTesting;
@@ -406,7 +407,7 @@
if (iterator.hasNext()) {
filterFlag.append(String.format(" %s=%s", GBENCHMARK_FILTER_OPTION, iterator.next()));
while (iterator.hasNext()) {
- filterFlag.append(String.format("\\|%s", iterator.next()));
+ filterFlag.append(String.format("|%s", iterator.next()));
}
}
return filterFlag.toString();
@@ -421,7 +422,7 @@
// Format benchmark as "^benchmark$" to avoid unintended regex partial matching.
filterFlag.append(String.format(" %s=^%s$", GBENCHMARK_FILTER_OPTION, iterator.next()));
while (iterator.hasNext()) {
- filterFlag.append(String.format("\\|^%s$", iterator.next()));
+ filterFlag.append(String.format("|^%s$", iterator.next()));
}
}
return filterFlag.toString();
@@ -441,14 +442,14 @@
final String cmd,
final IShellOutputReceiver outputReceiver)
throws DeviceNotAvailableException {
+ String shellCmd = StringEscapeUtils.escapeShell(cmd);
// Ensure that command is not too long for adb
- if (cmd.length() < ADB_CMD_CHAR_LIMIT) {
+ if (shellCmd.length() < ADB_CMD_CHAR_LIMIT) {
if (outputReceiver == null) {
- return testDevice.executeShellCommand(cmd);
+ return testDevice.executeShellCommand(shellCmd);
}
-
testDevice.executeShellCommand(
- cmd,
+ shellCmd,
outputReceiver,
mMaxRunTime /* maxTimeToShellOutputResponse */,
TimeUnit.MILLISECONDS,
@@ -457,7 +458,7 @@
}
// Wrap adb shell command in script if command is too long for direct execution
- return executeCommandByScript(testDevice, cmd, outputReceiver);
+ return executeCommandByScript(testDevice, shellCmd, outputReceiver);
}
/** Runs a command from a temporary script. */
diff --git a/test_framework/com/android/tradefed/testtype/HostGTest.java b/test_framework/com/android/tradefed/testtype/HostGTest.java
index 43d599e..3da63e4 100644
--- a/test_framework/com/android/tradefed/testtype/HostGTest.java
+++ b/test_framework/com/android/tradefed/testtype/HostGTest.java
@@ -41,21 +41,10 @@
/** A Test that runs a native test package. */
@OptionClass(alias = "hostgtest")
-public class HostGTest extends GTestBase implements IAbiReceiver, IBuildReceiver {
+public class HostGTest extends GTestBase implements IBuildReceiver {
private static final long DEFAULT_HOST_COMMAND_TIMEOUT_MS = 2 * 60 * 1000;
private IBuildInfo mBuildInfo = null;
- private IAbi mAbi = null;
-
- @Override
- public void setAbi(IAbi abi) {
- this.mAbi = abi;
- }
-
- @Override
- public IAbi getAbi() {
- return this.mAbi;
- }
@Override
public void setBuild(IBuildInfo buildInfo) {
@@ -216,7 +205,7 @@
String moduleName = getTestModule();
File gTestFile = null;
try {
- gTestFile = FileUtil.findFile(moduleName, mAbi, scanDirs.toArray(new File[] {}));
+ gTestFile = FileUtil.findFile(moduleName, getAbi(), scanDirs.toArray(new File[] {}));
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -226,7 +215,8 @@
// search for it with a potential suffix (which is allowed).
try {
File byBaseName =
- FileUtil.findFile(moduleName + ".*", mAbi, scanDirs.toArray(new File[] {}));
+ FileUtil.findFile(
+ moduleName + ".*", getAbi(), scanDirs.toArray(new File[] {}));
if (byBaseName != null && byBaseName.isFile()) {
gTestFile = byBaseName;
}
diff --git a/test_framework/com/android/tradefed/testtype/InstrumentationListener.java b/test_framework/com/android/tradefed/testtype/InstrumentationListener.java
index baafb76..6642922 100644
--- a/test_framework/com/android/tradefed/testtype/InstrumentationListener.java
+++ b/test_framework/com/android/tradefed/testtype/InstrumentationListener.java
@@ -135,7 +135,8 @@
super.testStarted(miss);
FailureDescription failure =
FailureDescription.create(
- "test did not run due to instrumentation issue.",
+ "test did not run due to instrumentation issue. See run level "
+ + "error for reason.",
FailureStatus.NOT_EXECUTED);
super.testFailed(miss, failure);
super.testEnded(miss, new HashMap<String, Metric>());
diff --git a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
index 75e82d7..5934799 100644
--- a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
+++ b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
@@ -16,13 +16,9 @@
package com.android.tradefed.testtype;
-import static com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain.CLANG;
-import static com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain.GCOV;
-import static com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain.JACOCO;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Verify.verify;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
@@ -37,14 +33,15 @@
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.metric.GcovCodeCoverageCollector;
import com.android.tradefed.device.metric.IMetricCollector;
import com.android.tradefed.device.metric.IMetricCollectorReceiver;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.BugreportCollector;
import com.android.tradefed.result.CollectingTestListener;
import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.ITestLifeCycleReceiver;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
@@ -52,13 +49,10 @@
import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.retry.IRetryDecision;
import com.android.tradefed.retry.RetryStrategy;
-import com.android.tradefed.testtype.coverage.CoverageOptions;
import com.android.tradefed.util.AbiFormatter;
import com.android.tradefed.util.ArrayUtil;
-import com.android.tradefed.util.JavaCodeCoverageFlusher;
import com.android.tradefed.util.ListInstrumentationParser;
import com.android.tradefed.util.ListInstrumentationParser.InstrumentationTarget;
-import com.android.tradefed.util.NativeCodeCoverageFlusher;
import com.android.tradefed.util.StringEscapeUtils;
import com.google.common.annotations.VisibleForTesting;
@@ -98,8 +92,7 @@
/** default timeout for tests collection */
static final long TEST_COLLECTION_TIMEOUT_MS = 2 * 60 * 1000;
- /** test run name for merging coverage measurements */
- static final String MERGE_COVERAGE_MEASUREMENTS_TEST_NAME = "mergeCoverageMeasurements";
+ static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER";
@Option(
name = "package",
@@ -260,12 +253,12 @@
)
private boolean mCoverage = false;
+ @Deprecated
@Option(
- name = "merge-coverage-measurements",
- description =
- "Merge coverage measurements from all test runs into a single measurement before "
- + "logging."
- )
+ name = "merge-coverage-measurements",
+ description =
+ "Merge coverage measurements from all test runs into a single measurement "
+ + "before logging.")
private boolean mMergeCoverageMeasurements = false;
@Deprecated
@@ -340,7 +333,7 @@
private String mTestFilePathOnDevice = null;
private ListInstrumentationParser mListInstrumentationParser = null;
- private NativeCodeCoverageListener mNativeCoverageListener = null;
+ private GcovCodeCoverageCollector mNativeCoverageListener = null;
private List<String> mExtraDeviceListener = new ArrayList<>();
@@ -649,12 +642,6 @@
return mForceAbi;
}
- /** Sets the --merge-coverage-measurements option for testing. */
- @VisibleForTesting
- void setMergeCoverageMeasurements(boolean merge) {
- mMergeCoverageMeasurements = merge;
- }
-
/** Sets the --rerun-from-file option. */
public void setReRunUsingTestFile(boolean reRunUsingTestFile) {
mReRunUsingTestFile = reRunUsingTestFile;
@@ -884,7 +871,8 @@
"Tests to run should not be set explicitly when --collect-tests-only is set.");
// Use the actual listener to collect the tests, and print a error if this fails
- Collection<TestDescription> collectedTests = collectTestsToRun(mRunner, listener);
+ Collection<TestDescription> collectedTests =
+ collectTestsToRun(testInfo, mRunner, listener);
if (collectedTests == null) {
CLog.e("Failed to collect tests for %s", mPackageName);
} else {
@@ -897,7 +885,7 @@
Collection<TestDescription> testsToRun = mTestsToRun;
if (testsToRun == null) {
// Don't notify the listener since it's not a real run.
- testsToRun = collectTestsToRun(mRunner, null);
+ testsToRun = collectTestsToRun(testInfo, mRunner, null);
}
// Only set the debug flag after collecting tests.
@@ -908,34 +896,9 @@
mRunner.addInstrumentationArg("coverage", "true");
}
- // Reruns do not create new listeners or clear coverage measurements.
+ // Reruns do not create new listeners.
if (!mIsRerun) {
listener = addBugreportListenerIfEnabled(listener);
- listener = addJavaCoverageListenerIfEnabled(listener);
- listener = addGcovCoverageListenerIfEnabled(listener);
- listener = addClangCoverageListenerIfEnabled(listener);
-
- // Clear coverage measurements on the device before running.
- if (mConfiguration != null
- && mConfiguration.getCoverageOptions().isCoverageFlushEnabled()) {
- CoverageOptions options = mConfiguration.getCoverageOptions();
-
- if (options.getCoverageToolchains().contains(GCOV)
- || options.getCoverageToolchains().contains(CLANG)) {
- // Enable abd root on the device, otherwise the following commands will fail.
- verify(mDevice.enableAdbRoot(), "Failed to enable adb root.");
-
- NativeCodeCoverageFlusher flusher =
- new NativeCodeCoverageFlusher(mDevice, options.getCoverageProcesses());
- flusher.resetCoverage();
- }
-
- if (options.getCoverageToolchains().contains(JACOCO)) {
- JavaCodeCoverageFlusher flusher =
- new JavaCodeCoverageFlusher(mDevice, options.getCoverageProcesses());
- flusher.resetCoverage();
- }
- }
// TODO: Convert to device-side collectors when possible.
for (IMetricCollector collector : mCollectors) {
@@ -945,6 +908,9 @@
CLog.d(
"Initializing %s for instrumentation.",
collector.getClass().getCanonicalName());
+ if (collector instanceof IConfigurationReceiver) {
+ ((IConfigurationReceiver) collector).setConfiguration(mConfiguration);
+ }
listener = collector.init(testInfo.getContext(), listener);
}
}
@@ -957,19 +923,26 @@
if (testsToRun == null) {
// Failed to collect the tests or collection is off. Just try to run them all.
- mDevice.runInstrumentationTests(mRunner, listener);
+ runInstrumentationTests(testInfo, mRunner, listener);
} else if (!testsToRun.isEmpty()) {
runWithRerun(testInfo, listener, testsToRun);
} else {
CLog.i("No tests expected for %s, skipping", mPackageName);
}
+ }
- // Merge coverage measurements after all tests have been run, but not inside the rerun
- // itself since the merging will be handled by the caller.
- if (!mIsRerun && mMergeCoverageMeasurements) {
- listener.testRunStarted(MERGE_COVERAGE_MEASUREMENTS_TEST_NAME, 0);
- listener.testRunEnded(0, new HashMap<String, Metric>());
+ private boolean runInstrumentationTests(
+ TestInformation testInfo,
+ IRemoteAndroidTestRunner runner,
+ ITestLifeCycleReceiver... receivers)
+ throws DeviceNotAvailableException {
+ if (testInfo != null && testInfo.properties().containsKey(RUN_TESTS_AS_USER_KEY)) {
+ return mDevice.runInstrumentationTestsAsUser(
+ runner,
+ Integer.parseInt(testInfo.properties().get(RUN_TESTS_AS_USER_KEY)),
+ receivers);
}
+ return mDevice.runInstrumentationTests(runner, receivers);
}
/**
@@ -991,61 +964,6 @@
}
/**
- * Returns a listener that will collect coverage measurements, or the original {@code listener}
- * if this feature is disabled.
- */
- ITestInvocationListener addJavaCoverageListenerIfEnabled(ITestInvocationListener listener) {
- if (mConfiguration == null) {
- return listener;
- }
- if (mConfiguration.getCoverageOptions().isCoverageEnabled()
- && mConfiguration.getCoverageOptions().getCoverageToolchains().contains(JACOCO)) {
- return new JavaCodeCoverageListener(
- getDevice(),
- mConfiguration.getCoverageOptions(),
- mMergeCoverageMeasurements,
- listener);
- }
- return listener;
- }
-
- /**
- * Returns a listener that will collect gcov coverage measurements, or the original {@code
- * listener} if this feature is disabled.
- */
- ITestInvocationListener addGcovCoverageListenerIfEnabled(ITestInvocationListener listener) {
- if (mConfiguration == null) {
- return listener;
- }
- if (mConfiguration.getCoverageOptions().isCoverageEnabled()
- && mConfiguration.getCoverageOptions().getCoverageToolchains().contains(GCOV)) {
- mNativeCoverageListener =
- new NativeCodeCoverageListener(
- getDevice(), mConfiguration.getCoverageOptions(), listener);
- return mNativeCoverageListener;
- }
- return listener;
- }
-
- /**
- * Returns a listener that will collect Clang coverage measurements, or the original {@code
- * listener} if this feature is disabled.
- */
- ITestInvocationListener addClangCoverageListenerIfEnabled(ITestInvocationListener listener) {
- if (mConfiguration == null) {
- return listener;
- }
- if (mConfiguration.getCoverageOptions().isCoverageEnabled()
- && mConfiguration.getCoverageOptions().getCoverageToolchains().contains(CLANG)) {
- ClangCodeCoverageListener clangListener =
- new ClangCodeCoverageListener(getDevice(), listener);
- clangListener.setConfiguration(mConfiguration);
- return clangListener;
- }
- return listener;
- }
-
- /**
* Execute the test run, but re-run incomplete tests individually if run fails to complete.
*
* @param listener the {@link ITestInvocationListener}
@@ -1067,7 +985,7 @@
getDevice().getProcessByName("system_server"));
}
instrumentationListener.setReportUnexecutedTests(mReportUnexecuted);
- mDevice.runInstrumentationTests(mRunner, instrumentationListener);
+ runInstrumentationTests(testInfo, mRunner, instrumentationListener);
TestRunResult testRun = testTracker.getCurrentRunResults();
if (testRun.isRunFailure() || !testRun.getCompletedTests().containsAll(expectedTests)) {
// Don't re-run any completed tests, unless this is a coverage run.
@@ -1168,7 +1086,9 @@
* @throws DeviceNotAvailableException
*/
private Collection<TestDescription> collectTestsToRun(
- final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener)
+ final TestInformation testInfo,
+ final IRemoteAndroidTestRunner runner,
+ final ITestInvocationListener listener)
throws DeviceNotAvailableException {
if (isRerunMode()) {
Log.d(LOG_TAG, String.format("Collecting test info for %s on device %s",
@@ -1178,7 +1098,7 @@
runner.setDebug(false);
// try to collect tests multiple times, in case device is temporarily not available
// on first attempt
- Collection<TestDescription> tests = collectTestsAndRetry(runner, listener);
+ Collection<TestDescription> tests = collectTestsAndRetry(testInfo, runner, listener);
// done with "logOnly" mode, restore proper test timeout before real test execution
addTimeoutsToRunner(runner);
runner.setTestCollection(false);
@@ -1198,7 +1118,9 @@
*/
@VisibleForTesting
Collection<TestDescription> collectTestsAndRetry(
- final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener)
+ final TestInformation testInfo,
+ final IRemoteAndroidTestRunner runner,
+ final ITestInvocationListener listener)
throws DeviceNotAvailableException {
boolean communicationFailure = false;
for (int i=0; i < COLLECT_TESTS_ATTEMPTS; i++) {
@@ -1207,9 +1129,9 @@
// We allow to override the ddmlib default timeout for collection of tests.
runner.setMaxTimeToOutputResponse(mCollectTestTimeout, TimeUnit.MILLISECONDS);
if (listener == null) {
- instrResult = mDevice.runInstrumentationTests(runner, collector);
+ instrResult = runInstrumentationTests(testInfo, runner, collector);
} else {
- instrResult = mDevice.runInstrumentationTests(runner, collector, listener);
+ instrResult = runInstrumentationTests(testInfo, runner, collector, listener);
}
TestRunResult runResults = collector.getCurrentRunResults();
if (!instrResult || !runResults.isRunComplete()) {
diff --git a/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java b/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java
index 337370e..ac89d5c 100644
--- a/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java
+++ b/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.testtype;
+import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.config.Option;
@@ -205,8 +206,11 @@
*/
private String compileClassPath() throws ClassNotFoundException {
List<String> paths = new ArrayList<>();
- IDeviceBuildInfo build = (IDeviceBuildInfo) mBuildInfo;
- File testDir = build.getTestsDir();
+ File testDir = mBuildInfo.getFile(BuildInfoFileKey.TESTDIR_IMAGE);
+
+ if (!testDir.exists()) {
+ throw new IllegalArgumentException("Test directory not found, cannot proceed");
+ }
// This is a relatively hacky way to get around the fact that we don't have a consistent
// way to locate tradefed related jars in all environments, so instead we dyn link to that
diff --git a/test_framework/com/android/tradefed/testtype/JavaCodeCoverageListener.java b/test_framework/com/android/tradefed/testtype/JavaCodeCoverageListener.java
deleted file mode 100644
index 0076620..0000000
--- a/test_framework/com/android/tradefed/testtype/JavaCodeCoverageListener.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * 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.testtype;
-
-import static com.google.common.base.Verify.verifyNotNull;
-import static com.google.common.io.Files.getNameWithoutExtension;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.result.ResultForwarder;
-import com.android.tradefed.testtype.coverage.CoverageOptions;
-import com.android.tradefed.util.FileUtil;
-import com.android.tradefed.util.JavaCodeCoverageFlusher;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableList;
-
-import org.jacoco.core.tools.ExecFileLoader;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * A {@link ResultForwarder} that will pull Java coverage measurements off of the device and log
- * them as test artifacts.
- */
-final class JavaCodeCoverageListener extends ResultForwarder {
-
- public static final String MERGE_COVERAGE_MEASUREMENTS_TEST_NAME = "mergeCoverageMeasurements";
- public static final String COVERAGE_MEASUREMENT_KEY = "coverageFilePath";
- public static final String COVERAGE_DIRECTORY = "/data/misc/trace";
- public static final String FIND_COVERAGE_FILES =
- String.format("find %s -name '*.ec'", COVERAGE_DIRECTORY);
-
- private final ITestDevice mDevice;
- private final CoverageOptions mCoverageOptions;
-
- private final boolean mMergeCoverageMeasurements;
-
- private final ExecFileLoader mExecFileLoader = new ExecFileLoader();
-
- private JavaCodeCoverageFlusher mFlusher;
- private String mCurrentRunName;
-
- public JavaCodeCoverageListener(
- ITestDevice device,
- CoverageOptions options,
- boolean mergeMeasurements,
- ITestInvocationListener... listeners) {
- super(listeners);
- mDevice = device;
- mCoverageOptions = options;
- mMergeCoverageMeasurements = mergeMeasurements;
-
- mFlusher = new JavaCodeCoverageFlusher(device, options.getCoverageProcesses());
- }
-
- @VisibleForTesting
- public void setCoverageFlusher(JavaCodeCoverageFlusher flusher) {
- mFlusher = flusher;
- }
-
- @Override
- public void testRunStarted(String runName, int testCount) {
- super.testRunStarted(runName, testCount);
- mCurrentRunName = runName;
- }
-
- @Override
- public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
- if (MERGE_COVERAGE_MEASUREMENTS_TEST_NAME.equals(mCurrentRunName)) {
- // Log the merged runtime coverage measurement.
- try {
- File mergedMeasurements =
- FileUtil.createTempFile(
- "merged_runtime_coverage_",
- "." + LogDataType.COVERAGE.getFileExt());
-
- mExecFileLoader.save(mergedMeasurements, false);
-
- // Save the merged measurement as a test log.
- logCoverageMeasurement("merged_runtime_coverage", mergedMeasurements);
- } catch (IOException e) {
- throw new RuntimeException(e);
- } finally {
- super.testRunEnded(elapsedTime, runMetrics);
- }
- } else {
- // Get the path of the coverage measurement on the device.
- Metric devicePathMetric = runMetrics.get(COVERAGE_MEASUREMENT_KEY);
- if (devicePathMetric == null) {
- super.testRunFailed("No Java code coverage measurement.");
- super.testRunEnded(elapsedTime, runMetrics);
- return;
- }
- String testCoveragePath = devicePathMetric.getMeasurements().getSingleString();
- if (testCoveragePath == null) {
- super.testRunFailed("No Java code coverage measurement.");
- super.testRunEnded(elapsedTime, runMetrics);
- return;
- }
-
- ImmutableList.Builder<String> devicePaths = ImmutableList.builder();
- devicePaths.add(testCoveragePath);
-
- try {
- if (mCoverageOptions.isCoverageFlushEnabled()) {
- mFlusher.forceCoverageFlush();
- }
-
- // Find all .ec files in /data/misc/trace and pull them from the device as well.
- String fileList = mDevice.executeShellCommand(FIND_COVERAGE_FILES);
- devicePaths.addAll(Splitter.on('\n').omitEmptyStrings().split(fileList));
-
- collectAndLogCoverageMeasurementsAsRoot(devicePaths.build());
-
- } catch (DeviceNotAvailableException | IOException e) {
- throw new RuntimeException(e);
- } finally {
- super.testRunEnded(elapsedTime, runMetrics);
- }
- }
- }
-
- private void logCoverageMeasurement(String name, File coverageFile) {
- try (FileInputStreamSource source = new FileInputStreamSource(coverageFile, true)) {
- testLog(name, LogDataType.COVERAGE, source);
- }
- }
-
- private void collectAndLogCoverageMeasurementsAsRoot(List<String> devicePaths)
- throws IOException, DeviceNotAvailableException {
-
- // We enable root before pulling files off the device since the coverage file of the test
- // process is written in its private directory which is otherwise inaccessible. Coverage
- // files of other processes should be accessible without root. Note that we also restore
- // root status to what it was after we're done to not interfere with subsequent tests that
- // run on the device.
- boolean wasRoot = mDevice.isAdbRoot();
- if (!wasRoot && !mDevice.enableAdbRoot()) {
- throw new RuntimeException(
- "Failed to enable root before pulling Java code coverage files off device");
- }
-
- try {
- collectAndLogCoverageMeasurements(devicePaths);
- } finally {
- for (String devicePath : devicePaths) {
- mDevice.deleteFile(devicePath);
- }
-
- if (!wasRoot && !mDevice.disableAdbRoot()) {
- throw new RuntimeException(
- "Failed to disable root after pulling Java code coverage files off device");
- }
- }
- }
-
- private void collectAndLogCoverageMeasurements(List<String> devicePaths)
- throws IOException, DeviceNotAvailableException {
-
- for (String devicePath : devicePaths) {
- File coverageFile = mDevice.pullFile(devicePath);
- verifyNotNull(
- coverageFile, "Failed to pull the Java code coverage file from %s", devicePath);
-
- // When merging, load the measurement data. Otherwise log the measurement
- // immediately.
- try {
- if (mMergeCoverageMeasurements) {
- mExecFileLoader.load(coverageFile);
- } else {
- logCoverageMeasurement(
- mCurrentRunName
- + "_"
- + getNameWithoutExtension(devicePath)
- + "_runtime_coverage",
- coverageFile);
- }
- } finally {
- FileUtil.deleteFile(coverageFile);
- }
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java b/test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java
deleted file mode 100644
index 54217ef..0000000
--- a/test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2019 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;
-
-import static com.google.common.base.Verify.verify;
-import static com.google.common.base.Verify.verifyNotNull;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.result.ResultForwarder;
-import com.android.tradefed.testtype.coverage.CoverageOptions;
-import com.android.tradefed.util.FileUtil;
-import com.android.tradefed.util.NativeCodeCoverageFlusher;
-import com.android.tradefed.util.TarUtil;
-import com.android.tradefed.util.ZipUtil;
-
-import com.google.common.collect.ImmutableList;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashMap;
-
-/**
- * A {@link ResultForwarder} that will pull native coverage measurements off of the device and log
- * them as test artifacts.
- */
-public final class NativeCodeCoverageListener extends ResultForwarder {
-
- private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace";
- private static final String COVERAGE_TAR_PATH =
- String.format("%s/coverage.tar", NATIVE_COVERAGE_DEVICE_PATH);
-
- // Finds .gcda files in /data/misc/trace and compresses those files only. Stores the full
- // path of the file on the device.
- private static final String ZIP_COVERAGE_FILES_COMMAND =
- String.format(
- "find %s -name '*.gcda' | tar -cvf %s -T -",
- NATIVE_COVERAGE_DEVICE_PATH, COVERAGE_TAR_PATH);
-
- // Deletes .gcda files in /data/misc/trace.
- private static final String DELETE_COVERAGE_FILES_COMMAND =
- String.format("find %s -name '*.gcda' -delete", NATIVE_COVERAGE_DEVICE_PATH);
-
- private final boolean mFlushCoverage;
- private final ITestDevice mDevice;
- private final NativeCodeCoverageFlusher mFlusher;
- private boolean mCollectCoverageOnTestEnd = true;
-
- private String mCurrentRunName;
-
- public NativeCodeCoverageListener(ITestDevice device, ITestInvocationListener... listeners) {
- super(listeners);
- mDevice = device;
- mFlushCoverage = false;
- mFlusher = new NativeCodeCoverageFlusher(mDevice, ImmutableList.of());
- }
-
- public NativeCodeCoverageListener(
- ITestDevice device,
- CoverageOptions coverageOptions,
- ITestInvocationListener... listeners) {
- super(listeners);
- mDevice = device;
- mFlushCoverage = coverageOptions.isCoverageFlushEnabled();
- mFlusher = new NativeCodeCoverageFlusher(mDevice, coverageOptions.getCoverageProcesses());
- }
-
- /**
- * Sets whether to collect coverage on testRunEnded.
- *
- * <p>Set this to false during re-runs, otherwise each individual test re-run will collect
- * coverage rather than having a single merged coverage result.
- */
- public void setCollectOnTestEnd(boolean collect) {
- mCollectCoverageOnTestEnd = collect;
- }
-
- @Override
- public void testRunStarted(String runName, int testCount) {
- super.testRunStarted(runName, testCount);
- mCurrentRunName = runName;
- }
-
- @Override
- public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
- try {
- if (mCollectCoverageOnTestEnd) {
- logCoverageMeasurements(mCurrentRunName);
- }
- } finally {
- super.testRunEnded(elapsedTime, runMetrics);
- }
- }
-
- /** Pulls native coverage measurements from the device and logs them. */
- public void logCoverageMeasurements(String runName) {
- File coverageTar = null;
- File coverageZip = null;
- try {
- // Enable abd root on the device, otherwise the following commands will fail.
- verify(mDevice.enableAdbRoot(), "Failed to enable adb root.");
-
- // Flush cross-process coverage.
- if (mFlushCoverage) {
- mFlusher.forceCoverageFlush();
- }
-
- // Compress coverage measurements on the device before pulling.
- mDevice.executeShellCommand(ZIP_COVERAGE_FILES_COMMAND);
- coverageTar = mDevice.pullFile(COVERAGE_TAR_PATH);
- verifyNotNull(
- coverageTar,
- "Failed to pull the native code coverage file %s",
- COVERAGE_TAR_PATH);
- mDevice.deleteFile(COVERAGE_TAR_PATH);
-
- coverageZip = convertTarToZip(coverageTar);
-
- try (FileInputStreamSource source = new FileInputStreamSource(coverageZip, true)) {
- testLog(runName + "_native_runtime_coverage", LogDataType.NATIVE_COVERAGE, source);
- }
-
- // Delete coverage files on the device.
- mDevice.executeShellCommand(DELETE_COVERAGE_FILES_COMMAND);
- } catch (DeviceNotAvailableException | IOException e) {
- throw new RuntimeException(e);
- } finally {
- FileUtil.deleteFile(coverageTar);
- FileUtil.deleteFile(coverageZip);
- }
- }
-
- /**
- * Converts a .tar file to a .zip file.
- *
- * @param tar the .tar file to convert
- * @return a .zip file with the same contents
- * @throws IOException
- */
- private File convertTarToZip(File tar) throws IOException {
- File untarDir = null;
- try {
- untarDir = FileUtil.createTempDir("gcov_coverage");
- TarUtil.unTar(tar, untarDir);
- return ZipUtil.createZip(Arrays.asList(untarDir.listFiles()), "native_coverage");
- } finally {
- FileUtil.recursiveDelete(untarDir);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/testtype/binary/ExecutableBaseTest.java b/test_framework/com/android/tradefed/testtype/binary/ExecutableBaseTest.java
index 94e4aa6..055d7f7 100644
--- a/test_framework/com/android/tradefed/testtype/binary/ExecutableBaseTest.java
+++ b/test_framework/com/android/tradefed/testtype/binary/ExecutableBaseTest.java
@@ -169,7 +169,8 @@
FailureDescription.create(
String.format(NO_BINARY_ERROR, cmd),
FailureStatus.TEST_FAILURE)
- .setErrorIdentifier(InfraErrorIdentifier.ARTIFACT_NOT_FOUND);
+ .setErrorIdentifier(
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
listener.testRunFailed(failure);
listener.testRunEnded(0L, new HashMap<String, Metric>());
} else {
@@ -303,6 +304,9 @@
// Set one test command per shard
shard.mTestCommands.put(testName, cmd);
}
+ // Copy the filters to each shard
+ shard.mExcludeFilters.addAll(mExcludeFilters);
+ shard.mIncludeFilters.addAll(mIncludeFilters);
} catch (InstantiationException
| IllegalAccessException
| InvocationTargetException
diff --git a/test_framework/com/android/tradefed/testtype/host/CoverageMeasurementForwarder.java b/test_framework/com/android/tradefed/testtype/host/CoverageMeasurementForwarder.java
index 591d2d8..11ebdaa 100644
--- a/test_framework/com/android/tradefed/testtype/host/CoverageMeasurementForwarder.java
+++ b/test_framework/com/android/tradefed/testtype/host/CoverageMeasurementForwarder.java
@@ -36,7 +36,7 @@
import java.util.HashMap;
import java.util.List;
-/** A dummy test that fowards coverage measurements from the build provider to the logger. */
+/** A placeholder test that forwards coverage measurements from the build provider to the logger. */
public final class CoverageMeasurementForwarder implements IRemoteTest {
@Option(
diff --git a/test_framework/com/android/tradefed/testtype/junit4/JUnit4ResultForwarder.java b/test_framework/com/android/tradefed/testtype/junit4/JUnit4ResultForwarder.java
index be3fce4..0407f17 100644
--- a/test_framework/com/android/tradefed/testtype/junit4/JUnit4ResultForwarder.java
+++ b/test_framework/com/android/tradefed/testtype/junit4/JUnit4ResultForwarder.java
@@ -15,9 +15,13 @@
*/
package com.android.tradefed.testtype.junit4;
+import com.android.tradefed.error.IHarnessException;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.FailureDescription;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.error.ErrorIdentifier;
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.LogAnnotation;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.MetricAnnotation;
import com.android.tradefed.testtype.MetricTestCase.LogHolder;
@@ -51,18 +55,29 @@
public void testFailure(Failure failure) throws Exception {
Description description = failure.getDescription();
if (description.getMethodName() == null) {
- String trace = failure.getTrace();
- if (failure.getException() instanceof CarryDnaeError) {
- trace =
- StreamUtil.getStackTrace(
- ((CarryDnaeError) failure.getException())
- .getDeviceNotAvailableException());
+ Throwable error = failure.getException();
+ String message = error.getMessage();
+ if (message == null) {
+ message = "Exception with no error message";
}
- // In case of exception in @BeforeClass, the method name will be null
- mListener.testRunFailed(String.format("Failed with trace: %s", trace));
+ FailureDescription failureDesc =
+ FailureDescription.create(message).setFailureStatus(FailureStatus.TEST_FAILURE);
+ if (error instanceof CarryDnaeError) {
+ error = ((CarryDnaeError) error).getDeviceNotAvailableException();
+ }
+ failureDesc.setCause(error);
+ if (error instanceof IHarnessException) {
+ ErrorIdentifier id = ((IHarnessException) error).getErrorId();
+ if (id != null) {
+ failureDesc.setFailureStatus(id.status());
+ }
+ failureDesc.setErrorIdentifier(((IHarnessException) error).getErrorId());
+ failureDesc.setOrigin(((IHarnessException) error).getOrigin());
+ }
+ mListener.testRunFailed(failureDesc);
// If the exception is ours thrown from before, rethrow it
- if (failure.getException() instanceof CarryDnaeError) {
- throw ((CarryDnaeError) failure.getException()).getDeviceNotAvailableException();
+ if (error instanceof CarryDnaeError) {
+ throw ((CarryDnaeError) error).getDeviceNotAvailableException();
}
return;
}
diff --git a/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java b/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
index 67cd44c..950f8df 100644
--- a/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
+++ b/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
@@ -24,10 +24,13 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.FailureDescription;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.targetprep.adb.AdbStopServerPreparer;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IDeviceTest;
@@ -37,6 +40,7 @@
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.PythonVirtualenvHelper;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.StreamUtil;
@@ -48,6 +52,7 @@
import java.io.InputStream;
import java.io.Writer;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -136,7 +141,11 @@
@Override
public final void run(ITestInvocationListener listener) {
- List<File> parFilesList = findParFiles();
+ List<File> parFilesList = findParFiles(listener);
+ File venvDir = mBuildInfo.getFile("VIRTUAL_ENV");
+ if (venvDir != null) {
+ PythonVirtualenvHelper.activate(getRunUtil(), venvDir);
+ }
for (File parFile : parFilesList) {
// TODO(b/159365341): add a failure reporting for nonexistent binary.
if (!parFile.exists()) {
@@ -153,9 +162,13 @@
reportLogs(getLogDir(), listener);
}
}
+ if (venvDir != null
+ && venvDir.getAbsolutePath().startsWith(System.getProperty("java.io.tmpdir"))) {
+ FileUtil.recursiveDelete(venvDir);
+ }
}
- private List<File> findParFiles() {
+ private List<File> findParFiles(ITestInvocationListener listener) {
File testsDir = null;
if (mBuildInfo instanceof IDeviceBuildInfo) {
testsDir = ((IDeviceBuildInfo) mBuildInfo).getTestsDir();
@@ -169,6 +182,8 @@
res = FileUtil.findFile(testsDir, binaryName);
}
if (res == null) {
+ reportFailure(
+ listener, binaryName, "Couldn't find Mobly test binary " + binaryName);
throw new RuntimeException(
String.format("Couldn't find a par file %s", binaryName));
}
@@ -251,31 +266,43 @@
InputStream inputStream = null;
try {
inputStream = new FileInputStream(yamlSummaryFile);
- processYamlTestResults(inputStream, parser);
+ processYamlTestResults(inputStream, parser, listener, runName);
} catch (FileNotFoundException ex) {
- // TODO(b/159367088): report a test failure.
- CLog.e("Fail processing test results: ", ex);
+ reportFailure(
+ listener,
+ runName,
+ "Fail processing test results, result file not found.\n" + ex);
} finally {
StreamUtil.close(inputStream);
}
}
+ /**
+ * Parses Mobly test results and does result reporting.
+ *
+ * @param inputStream An InputStream object reading in Mobly test result file.
+ * @param parser An MoblyYamlResultParser object that processes Mobly test results.
+ * @param listener An ITestInvocationListener instance that does various reporting.
+ * @param runName str, the name of the Mobly test binary run.
+ */
@VisibleForTesting
- protected void processYamlTestResults(InputStream inputStream, MoblyYamlResultParser parser) {
+ protected void processYamlTestResults(
+ InputStream inputStream,
+ MoblyYamlResultParser parser,
+ ITestInvocationListener listener,
+ String runName) {
try {
parser.parse(inputStream);
} catch (MoblyYamlResultHandlerFactory.InvalidResultTypeException
| IllegalAccessException
| InstantiationException ex) {
- // TODO(b/159367088): report a test failure.
- CLog.e("Failed to parse result file: %s", ex);
+ reportFailure(listener, runName, "Failed to parse the result file.\n" + ex);
}
}
private void updateConfigFile() {
InputStream inputStream = null;
FileWriter fileWriter = null;
- // TODO(b/159369745): clean up the tmp files created.
File localConfigFile = new File(getLogDir(), "local_config.yaml");
try {
inputStream = new FileInputStream(mConfigFile);
@@ -334,6 +361,15 @@
return mLogDir;
}
+ private void reportFailure(
+ ITestInvocationListener listener, String runName, String errorMessage) {
+ listener.testRunStarted(runName, 0);
+ FailureDescription description =
+ FailureDescription.create(errorMessage, FailureStatus.TEST_FAILURE);
+ listener.testRunFailed(description);
+ listener.testRunEnded(0L, new HashMap<String, Metric>());
+ }
+
@VisibleForTesting
String getLogDirAbsolutePath() {
return getLogDir().getAbsolutePath();
@@ -348,6 +384,8 @@
protected String[] buildCommandLineArray(String filePath) {
List<String> commandLine = new ArrayList<>();
commandLine.add(filePath);
+ // TODO(b/166468397): some test binaries are actually a wrapper of Mobly runner and need --
+ // to separate Python options.
commandLine.add("--");
if (getConfigPath() != null) {
commandLine.add("--config=" + getConfigPath());
diff --git a/test_framework/com/android/tradefed/testtype/rust/RustBinaryHostTest.java b/test_framework/com/android/tradefed/testtype/rust/RustBinaryHostTest.java
index 42187c5..9723fe9 100644
--- a/test_framework/com/android/tradefed/testtype/rust/RustBinaryHostTest.java
+++ b/test_framework/com/android/tradefed/testtype/rust/RustBinaryHostTest.java
@@ -38,7 +38,6 @@
import java.io.File;
import java.io.IOException;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -128,42 +127,70 @@
}
private void runSingleRustFile(ITestInvocationListener listener, File file) {
+ CLog.d("Run single Rust File: %s", file.getAbsolutePath());
+ // Rust binary does not support multiple inclusion filters,
+ // so we run the test once for each include filter.
+ List<String> includeFilters = getListOfIncludeFilters();
+
+ // Call with --list once per include filter to add up testCount.
+ // Duplicated test cases selected by different include filters should not be counted.
+ Set<String> foundTests = new HashSet<>();
+ for (String filter : includeFilters) {
+ countTests(file, filter, foundTests);
+ }
+ int testCount = foundTests.size();
+ CLog.d("Total test count: %d", testCount);
+ long startTimeMs = System.currentTimeMillis();
+ listener.testRunStarted(file.getName(), testCount, 0, startTimeMs);
+ for (String filter : includeFilters) {
+ try {
+ runTestWithFilter(listener, file, filter);
+ } catch (IOException e) {
+ listener.testRunFailed(e.getMessage());
+ long testTimeMs = System.currentTimeMillis() - startTimeMs;
+ listener.testRunEnded(testTimeMs, new HashMap<String, Metric>());
+ throw new RuntimeException(e);
+ }
+ }
+ long testTimeMs = System.currentTimeMillis() - startTimeMs;
+ listener.testRunEnded(testTimeMs, new HashMap<String, Metric>());
+ }
+
+ private void countTests(File file, String filter, Set<String> foundTests) {
+ CLog.d("Count with filter '%s' for Rust File: %s", filter, file.getAbsolutePath());
List<String> commandLine = new ArrayList<>();
commandLine.add(file.getAbsolutePath());
- CLog.d("Run single Rust File: " + file.getAbsolutePath());
-
- // Add all the other options and include/exclude filters.
commandLine.addAll(mTestOptions);
- addFiltersToArgs(commandLine);
+ addFiltersToArgs(commandLine, filter);
List<String> listCommandLine = new ArrayList<>(commandLine);
listCommandLine.add("--list");
CommandResult listResult =
getRunUtil()
.runTimedCmdSilently(mTestTimeout, listCommandLine.toArray(new String[0]));
- int testCount = 0;
// TODO: Do we want to handle non-standard test harnesses without a
// --list param? Currently we will report 0 tests, which will cause an
// overall failure, but we don't know how to parse arbitrary test
// harness results.
if (listResult.getStatus() == CommandStatus.SUCCESS) {
- try {
- testCount = parseTestListCount(listResult.getStdout().split("\n"));
- } catch (ParseException e) {
- CLog.w("Parsing test list failed: %s", e.getMessage());
- }
+ collectTestLines(listResult.getStdout().split("\n"), foundTests);
} else {
CLog.w(
"Could not run command '%s' to get test list.",
String.join(" ", listCommandLine));
}
+ }
+ private void runTestWithFilter(ITestInvocationListener listener, File file, String filter)
+ throws IOException {
String runName = file.getName();
- long startTimeMs = System.currentTimeMillis();
- listener.testRunStarted(runName, testCount, 0, startTimeMs);
+ List<String> commandLine = new ArrayList<>();
+ commandLine.add(file.getAbsolutePath());
+ commandLine.addAll(mTestOptions);
+ addFiltersToArgs(commandLine, filter);
+ CLog.d("Running test with filter '%s'", filter);
CommandResult result =
getRunUtil().runTimedCmd(mTestTimeout, commandLine.toArray(new String[0]));
- long testTimeMs = System.currentTimeMillis() - startTimeMs;
if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
String message =
String.format(
@@ -185,17 +212,15 @@
String.format(RUST_LOG_STDERR_FORMAT, runName), LogDataType.TEXT, data);
}
String[] lines = result.getStdout().split("\n");
- new RustTestResultParser(listener, runName).processNewLines(lines);
+ RustTestResultParser parser = new RustTestResultParser(listener, runName);
+ parser.processNewLines(lines);
+ parser.done();
} catch (RuntimeException e) {
listener.testRunFailed(
String.format("Failed to parse the rust test output: %s", e.getMessage()));
CLog.e(e);
- } catch (IOException e) {
- listener.testRunFailed(e.getMessage());
- throw new RuntimeException(e);
} finally {
FileUtil.deleteFile(resultFile);
- listener.testRunEnded(testTimeMs, new HashMap<String, Metric>());
}
}
diff --git a/test_framework/com/android/tradefed/testtype/rust/RustBinaryTest.java b/test_framework/com/android/tradefed/testtype/rust/RustBinaryTest.java
index a35b722..7ff34c3 100644
--- a/test_framework/com/android/tradefed/testtype/rust/RustBinaryTest.java
+++ b/test_framework/com/android/tradefed/testtype/rust/RustBinaryTest.java
@@ -16,10 +16,6 @@
package com.android.tradefed.testtype.rust;
-import static com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain.GCOV;
-
-import static com.google.common.base.Verify.verify;
-
import com.android.ddmlib.FileListingService;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.tradefed.config.IConfiguration;
@@ -33,13 +29,12 @@
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.testtype.IDeviceTest;
-import com.android.tradefed.testtype.NativeCodeCoverageListener;
-import com.android.tradefed.testtype.coverage.CoverageOptions;
-import com.android.tradefed.util.NativeCodeCoverageFlusher;
import java.io.File;
-import java.text.ParseException;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
/** A Test that runs a rust binary on given device. */
@@ -155,38 +150,45 @@
final IShellOutputReceiver resultParser,
final String fullPath)
throws DeviceNotAvailableException {
- // TODO(chh): add rerun support
CLog.d("RustBinaryTest runTest: " + fullPath);
- // TODO(chh): add LD_LIBRARY_PATH
- String cmd;
- if (getCoverageOptions().isCoverageEnabled()) {
- cmd = "GCOV_PREFIX=/data/misc/trace/testcoverage " + fullPath;
- } else {
- cmd = fullPath;
- }
- cmd = addFiltersToCommand(cmd);
+ String cmd = fullPath;
- int testCount = 0;
- try {
- String[] testList = testDevice.executeShellCommand(cmd + " --list").split("\n");
- testCount = parseTestListCount(testList);
- } catch (DeviceNotAvailableException e) {
- CLog.e("Could not retrieve tests list from device: %s", e.getMessage());
- throw e;
- } catch (ParseException e) {
- CLog.w("Parsing test list failed: %s", e.getMessage());
+ // Rust binary does not support multiple inclusion filters,
+ // so we run the test once for each include filter.
+ List<String> includeFilters = getListOfIncludeFilters();
+
+ // Call with --list once per include filter to add up testCount.
+ // Duplicated test cases selected by different include filters should not be counted.
+ Set<String> foundTests = new HashSet<>();
+ for (String filter : includeFilters) {
+ String newCmd = addFiltersToCommand(cmd, filter);
+ try {
+ String[] testList = testDevice.executeShellCommand(newCmd + " --list").split("\n");
+ collectTestLines(testList, foundTests);
+ } catch (DeviceNotAvailableException e) {
+ CLog.e("Could not retrieve tests list from device: %s", e.getMessage());
+ throw e;
+ }
}
+ int testCount = foundTests.size();
+ CLog.d("Total test count: %d", testCount);
long startTimeMs = System.currentTimeMillis();
listener.testRunStarted(new File(fullPath).getName(), testCount, 0, startTimeMs);
- try {
- testDevice.executeShellCommand(
- cmd, resultParser, mTestTimeout, TimeUnit.MILLISECONDS, 0 /* retryAttempts */);
- } catch (DeviceNotAvailableException e) {
- listener.testRunFailed(String.format("Device not available: %s", e.getMessage()));
- } finally {
- listener.testRunEnded(
- System.currentTimeMillis() - startTimeMs, new HashMap<String, Metric>());
+ for (String filter : includeFilters) {
+ String newCmd = addFiltersToCommand(cmd, filter);
+ try {
+ testDevice.executeShellCommand(
+ newCmd,
+ resultParser,
+ mTestTimeout,
+ TimeUnit.MILLISECONDS,
+ 0 /* retryAttempts */);
+ } catch (DeviceNotAvailableException e) {
+ listener.testRunFailed(String.format("Device not available: %s", e.getMessage()));
+ }
}
+ long testTimeMs = System.currentTimeMillis() - startTimeMs;
+ listener.testRunEnded(testTimeMs, new HashMap<String, Metric>());
}
private void wrongTestPath(String msg, String testPath, ITestInvocationListener listener) {
@@ -212,56 +214,10 @@
return;
}
- // Insert the coverage listener if code coverage collection is enabled.
- listener = addNativeCoverageListenerIfEnabled(listener);
- NativeCodeCoverageFlusher flusher =
- new NativeCodeCoverageFlusher(mDevice, getCoverageOptions().getCoverageProcesses());
-
- if (getCoverageOptions().isCoverageEnabled()) {
- // Enable abd root on the device, otherwise the following commands will fail.
- // TODO(b/159843590): Restore adb root state later.
- verify(mDevice.enableAdbRoot(), "Failed to enable adb root.");
-
- flusher.resetCoverage();
-
- // Clang will no longer create directories that are part of the GCOV_PREFIX
- // environment variable. Force create the /data/misc/trace/testcoverage dir to
- // prevent "No such file or directory" errors when writing test coverage to disk.
- mDevice.executeShellCommand("mkdir /data/misc/trace/testcoverage");
- }
-
CLog.d("To run tests in directory " + testPath);
if (!doRunAllTestsInSubdirectory(testPath, mDevice, listener)) {
wrongTestPath("No test found under ", testPath, listener);
}
}
-
- /**
- * Returns the {@link CoverageOptions} for this test, if it exists. Otherwise returns a default
- * {@link CoverageOptions} object with all coverage disabled.
- */
- protected CoverageOptions getCoverageOptions() {
- if (mConfiguration != null) {
- return mConfiguration.getCoverageOptions();
- }
- return new CoverageOptions();
- }
-
- /**
- * Adds a listener to pull native code coverage measurements from the device after the test is
- * complete if coverage is enabled, otherwise returns the same listener.
- *
- * @param listener the current chain of listeners
- * @return a native coverage listener if coverage is enabled, otherwise the original listener
- */
- private ITestInvocationListener addNativeCoverageListenerIfEnabled(
- ITestInvocationListener listener) {
- CoverageOptions options = getCoverageOptions();
-
- if (options.isCoverageEnabled() && options.getCoverageToolchains().contains(GCOV)) {
- return new NativeCodeCoverageListener(mDevice, options, listener);
- }
- return listener;
- }
}
diff --git a/test_framework/com/android/tradefed/testtype/rust/RustTestBase.java b/test_framework/com/android/tradefed/testtype/rust/RustTestBase.java
index aa84c00..1e507dd 100644
--- a/test_framework/com/android/tradefed/testtype/rust/RustTestBase.java
+++ b/test_framework/com/android/tradefed/testtype/rust/RustTestBase.java
@@ -25,12 +25,11 @@
import com.google.common.annotations.VisibleForTesting;
-import java.text.ParseException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Base class of RustBinaryHostTest and RustBinaryTest */
@@ -51,12 +50,10 @@
isTimeVal = true)
protected long mTestTimeout = 20 * 1000L; // milliseconds
- @Option(
- name = "include-filter",
- description = "A substr filter of the test names to run; only the first one is used.")
+ @Option(name = "include-filter", description = "A substr filter of test case names to run.")
private Set<String> mIncludeFilters = new LinkedHashSet<>();
- @Option(name = "exclude-filter", description = "A substr filter of the test names to skip.")
+ @Option(name = "exclude-filter", description = "A substr filter of test case names to skip.")
private Set<String> mExcludeFilters = new LinkedHashSet<>();
// A wrapper that can be redefined in unit tests to create a (mocked) result parser.
@@ -65,35 +62,6 @@
return new RustTestResultParser(listener, runName);
}
- /**
- * Parse and return the number of tests from the list of tests from a Rust test harness.
- *
- * @param testList the output of the Rust test list (output of `test_binary --list`).
- */
- protected static int parseTestListCount(String[] testList) throws ParseException {
- // Get the number of tests we are running, assuming this is a standard
- // rust test harness. If this isn't, this command will fail and we will
- // report 0 tests, which is fine.
- int testCount = 0;
- if (testList.length > 0) {
- Matcher matcher = TEST_LIST_PATTERN.matcher(testList[testList.length - 1]);
- if (matcher.matches()) {
- testCount = Integer.parseInt(matcher.group(1));
- } else {
- throw new ParseException(
- "Could not match total test/benchmark count output. "
- + "Does this test use the standard Rust test harness?",
- 0);
- }
- } else {
- throw new ParseException(
- "Test did not return any output with --list argument. "
- + "Does this test use the standard Rust test harness?",
- 0);
- }
- return testCount;
- }
-
/** {@inheritDoc} */
@Override
public void addIncludeFilter(String filter) {
@@ -142,31 +110,48 @@
return mExcludeFilters;
}
- private void checkMultipleIncludeFilters() {
- if (mIncludeFilters.size() > 1) {
- CLog.e("Found multiple include filters; all except the 1st are ignored.");
+ /** Find test case names in testList and add them into foundTests. */
+ protected static void collectTestLines(String[] testList, Set<String> foundTests) {
+ // Rust test --list returns "testName: test" for each test.
+ int counter = 0;
+ for (String line : testList) {
+ if (line.endsWith(": test")) {
+ counter++;
+ foundTests.add(line);
+ }
}
+ CLog.d("Found %d tests", counter);
}
- protected void addFiltersToArgs(List<String> args) {
- checkMultipleIncludeFilters();
- for (String s : mIncludeFilters) {
- args.add(s);
+ /** Convert TestBinaryName#TestCaseName to TestCaseName for Rust Test. */
+ protected String cleanFilter(String filter) {
+ return filter.replaceFirst(".*#", "");
+ }
+
+ protected List<String> getListOfIncludeFilters() {
+ if (mIncludeFilters.isEmpty()) {
+ // Run test only once without any include filter.
+ return new ArrayList<String>(Arrays.asList(""));
+ }
+ return new ArrayList<String>(mIncludeFilters);
+ }
+
+ protected void addFiltersToArgs(List<String> args, String filter) {
+ if (!"".equals(filter)) {
+ args.add(cleanFilter(filter));
}
for (String s : mExcludeFilters) {
args.add("--skip");
- args.add(s);
+ args.add(cleanFilter(s));
}
}
- protected String addFiltersToCommand(String cmd) {
- if (!mIncludeFilters.isEmpty()) {
- checkMultipleIncludeFilters();
- cmd += " " + String.join(" ", mIncludeFilters);
+ protected String addFiltersToCommand(String cmd, String filter) {
+ List<String> args = new ArrayList<>();
+ addFiltersToArgs(args, filter);
+ if (args.isEmpty()) {
+ return cmd;
}
- if (!mExcludeFilters.isEmpty()) {
- cmd += " --skip " + String.join(" --skip ", mExcludeFilters);
- }
- return cmd;
+ return cmd + " " + String.join(" ", args);
}
}
diff --git a/test_framework/com/android/tradefed/testtype/rust/RustTestResultParser.java b/test_framework/com/android/tradefed/testtype/rust/RustTestResultParser.java
index 9807a5d..438533e 100644
--- a/test_framework/com/android/tradefed/testtype/rust/RustTestResultParser.java
+++ b/test_framework/com/android/tradefed/testtype/rust/RustTestResultParser.java
@@ -90,34 +90,15 @@
mTestResultCache = new HashMap<>();
}
- /**
- * Process Rust unittest output and report parsed results.
- *
- * <p>This method should be called only once with the full output, unlike the base method in
- * {@link MultiLineReceiver}.
- */
+ /** Process Rust unittest output. */
@Override
public void processNewLines(String[] lines) {
- try {
- boolean hasContent = false;
- for (String line : lines) {
- hasContent |= line.length() > 0;
- if (lineMatchesPattern(line, COMPLETE_PATTERN)) {
- reportToListeners(line);
- return;
- }
- if (lineMatchesPattern(line, RUST_ONE_LINE_RESULT)) {
- mCurrentTestName = mCurrentMatcher.group(1);
- mCurrentTestStatus = mCurrentMatcher.group(2);
- reportTestResult();
- }
+ for (String line : lines) {
+ if (lineMatchesPattern(line, RUST_ONE_LINE_RESULT)) {
+ mCurrentTestName = mCurrentMatcher.group(1);
+ mCurrentTestStatus = mCurrentMatcher.group(2);
+ reportTestResult();
}
- if (hasContent) {
- throw new Exception(
- String.format("Missing summary line:\n%s\n", String.join("\n", lines)));
- }
- } catch (Exception e) {
- throw new RuntimeException("Failed to parse Rust test result\n" + e);
}
}
@@ -144,7 +125,8 @@
}
/** Send recorded test results to all listeners. */
- private void reportToListeners(String line) {
+ @Override
+ public void done() {
for (ITestInvocationListener listener : mListeners) {
for (Entry<TestDescription, String> test : mTestResultCache.entrySet()) {
listener.testStarted(test.getKey());
diff --git a/test_framework/com/android/tradefed/util/PythonVirtualenvHelper.java b/test_framework/com/android/tradefed/util/PythonVirtualenvHelper.java
new file mode 100644
index 0000000..35c4190
--- /dev/null
+++ b/test_framework/com/android/tradefed/util/PythonVirtualenvHelper.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.io.File;
+import java.util.stream.Stream;
+
+/** A helper class for activating Python 3 virtual environment. */
+public class PythonVirtualenvHelper {
+
+ private static final String PATH = "PATH";
+ private static final String PYTHONHOME = "PYTHONHOME";
+ private static final String PYTHONPATH = "PYTHONPATH";
+ public static final String VIRTUAL_ENV = "VIRTUAL_ENV";
+
+ /**
+ * Gets python bin directory path.
+ *
+ * <p>This method will check the directory existence.
+ *
+ * @return str, the path to the python bin directory in venv.
+ * @throws NullPointerException if arg virtualenvPath is null.
+ * @throws RuntimeException if /path/to/venv/bin does not exist.
+ */
+ public static String getPythonBinDir(String virtualenvPath) {
+ if (virtualenvPath == null) {
+ throw new NullPointerException(
+ "Path to the Python virtual environment should not be null");
+ }
+ File res = new File(virtualenvPath, "bin");
+ if (!res.exists()) {
+ throw new RuntimeException("Invalid python virtualenv path " + res.getAbsolutePath());
+ }
+ return res.getAbsolutePath();
+ }
+
+ /**
+ * Activate virtualenv for a RunUtil.
+ *
+ * @param runUtil an utility object for running virtualenv activation commands.
+ * @param virtualenvDir a File object representing the created virtualenv directory.
+ */
+ public static void activate(IRunUtil runUtil, File virtualenvDir) {
+ activate(runUtil, virtualenvDir.getAbsolutePath());
+ }
+
+ /**
+ * Activate virtualenv for a RunUtil.
+ *
+ * <p>This method will check for python bin directory existence
+ *
+ * @param runUtil an utility object for running virtualenv activation commands.
+ * @param virtualenvPath the path to the created virtualenv directory.
+ */
+ public static void activate(IRunUtil runUtil, String virtualenvPath) {
+ String pythonBinDir = getPythonBinDir(virtualenvPath);
+ String separater = ":";
+ String pythonPath =
+ getPackageInstallLocation(runUtil, virtualenvPath)
+ + separater
+ + System.getenv(PYTHONPATH);
+ runUtil.setEnvVariable(PATH, pythonBinDir + separater + System.getenv().get(PATH));
+ runUtil.setEnvVariable(VIRTUAL_ENV, virtualenvPath);
+ runUtil.setEnvVariable(PYTHONPATH, pythonPath);
+ runUtil.unsetEnvVariable(PYTHONHOME);
+ CLog.d("Activating virtual environment:");
+ CLog.d("%s: %s", PATH, pythonBinDir + separater + System.getenv().get(PATH));
+ CLog.d("%s: %s", VIRTUAL_ENV, virtualenvPath);
+ CLog.d("%s: %s", PYTHONPATH, pythonPath);
+ }
+
+ /**
+ * Gets the absolute path to the pip3 binary in the given venv directory.
+ *
+ * @param virtualenvPath the path to the venv directory.
+ * @return a string representing the absolute path to the pip3 binary.
+ */
+ private static String getPipPath(String virtualenvPath) {
+ File pipFile = new File(PythonVirtualenvHelper.getPythonBinDir(virtualenvPath), "pip3");
+ return pipFile.getAbsolutePath();
+ }
+
+ /**
+ * Gets python package install location.
+ *
+ * <p>This method will call /path/to/venv/bin/pip3 show pip and parse out package location from
+ * stdout output.
+ *
+ * @param runUtil an utility object for running for running commands.
+ * @param virtualenvPath the path to the created virtualenv directory.
+ * @return a string representing the absolute path to the location where Python packages are
+ * installed.
+ */
+ private static String getPackageInstallLocation(IRunUtil runUtil, String virtualenvPath) {
+ CommandResult result =
+ runUtil.runTimedCmd(60000, getPipPath(virtualenvPath), "show", "pip");
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ throw new RuntimeException(
+ String.format(
+ "Fail to run command: %s show pip.\nStatus:%s\nStdout:%s\nStderr:%s",
+ getPipPath(virtualenvPath),
+ result.getStatus(),
+ result.getStdout(),
+ result.getStderr()));
+ }
+ String stdout = result.getStdout();
+ String[] lines = stdout.split("\n");
+ String locationLine =
+ Stream.of(lines).filter(x -> x.startsWith("Location")).findFirst().orElse("");
+ return locationLine.split(" ")[1];
+ }
+}
diff --git a/test_result_interfaces/com/android/tradefed/result/MultiFailureDescription.java b/test_result_interfaces/com/android/tradefed/result/MultiFailureDescription.java
index 5124910..b58972a 100644
--- a/test_result_interfaces/com/android/tradefed/result/MultiFailureDescription.java
+++ b/test_result_interfaces/com/android/tradefed/result/MultiFailureDescription.java
@@ -49,7 +49,11 @@
* @return The current {@link MultiFailureDescription}.
*/
public MultiFailureDescription addFailure(FailureDescription failure) {
- mFailures.add(failure);
+ if (failure instanceof MultiFailureDescription) {
+ addMultiFailures(((MultiFailureDescription) failure).getFailures());
+ } else {
+ mFailures.add(failure);
+ }
return this;
}
diff --git a/test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java b/test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java
deleted file mode 100644
index c3ef3a5..0000000
--- a/test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2020 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.result.error;
-
-import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
-
-/** Error Identifiers from Device errors and device reported errors. */
-public enum DeviceErrorIdentifier implements ErrorIdentifier {
-
- // ********************************************************************************************
- // Device Errors: 30_001 ~ 40_000
- // ********************************************************************************************
- APK_INSTALLATION_FAILED(30_001, FailureStatus.UNSET),
-
- AAPT_PARSER_FAILED(30_050, FailureStatus.UNSET),
-
- SHELL_COMMAND_ERROR(30_100, FailureStatus.UNSET),
- DEVICE_UNEXPECTED_RESPONSE(30_101, FailureStatus.UNSET),
-
- INSTRUMENATION_CRASH(30_200, FailureStatus.UNSET),
-
- FAILED_TO_LAUNCH_GCE(30_500, FailureStatus.LOST_SYSTEM_UNDER_TEST),
- FAILED_TO_CONNECT_TO_GCE(30_501, FailureStatus.LOST_SYSTEM_UNDER_TEST),
- ERROR_AFTER_FLASHING(30_502, FailureStatus.LOST_SYSTEM_UNDER_TEST),
-
- DEVICE_UNAVAILABLE(30_750, FailureStatus.LOST_SYSTEM_UNDER_TEST),
- DEVICE_UNRESPONSIVE(30_751, FailureStatus.LOST_SYSTEM_UNDER_TEST);
-
- private final long code;
- private final FailureStatus status;
-
- DeviceErrorIdentifier(int code, FailureStatus status) {
- this.code = code;
- this.status = status;
- }
-
- @Override
- public long code() {
- return code;
- }
-
- @Override
- public FailureStatus status() {
- return status;
- }
-}
diff --git a/test_result_interfaces/com/android/tradefed/result/error/InfraErrorIdentifier.java b/test_result_interfaces/com/android/tradefed/result/error/InfraErrorIdentifier.java
deleted file mode 100644
index 3001231..0000000
--- a/test_result_interfaces/com/android/tradefed/result/error/InfraErrorIdentifier.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 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.result.error;
-
-import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
-
-/** Error Identifiers from Trade Federation infra, and dependent infra (like Build infra). */
-public enum InfraErrorIdentifier implements ErrorIdentifier {
-
- // ********************************************************************************************
- // Infra: 10_001 ~ 20_000
- // ********************************************************************************************
- // 10_001 - 10_500: General errors
- ARTIFACT_NOT_FOUND(10_001, FailureStatus.INFRA_FAILURE),
- FAIL_TO_CREATE_FILE(10_002, FailureStatus.INFRA_FAILURE),
-
- // 10_501 - 11_000: Build, Artifacts download related errors
- ARTIFACT_REMOTE_PATH_NULL(10_501, FailureStatus.INFRA_FAILURE),
- ARTIFACT_UNSUPPORTED_PATH(10_502, FailureStatus.INFRA_FAILURE),
- ARTIFACT_DOWNLOAD_ERROR(10_503, FailureStatus.INFRA_FAILURE),
-
- // 11_001 - 11_500: environment issues: For example: lab wifi
- WIFI_FAILED_CONNECT(11_001, FailureStatus.UNSET), // TODO: switch to dependency_issue
-
- // 12_000 - 12_100: Test issues detected by infra
- EXPECTED_TESTS_MISMATCH(12_000, FailureStatus.TEST_FAILURE),
-
- UNDETERMINED(20_000, FailureStatus.UNSET);
-
- private final long code;
- private final FailureStatus status;
-
- InfraErrorIdentifier(int code, FailureStatus status) {
- this.code = code;
- this.status = status;
- }
-
- @Override
- public long code() {
- return code;
- }
-
- @Override
- public FailureStatus status() {
- return status;
- }
-}
diff --git a/tests/Android.bp b/tests/Android.bp
index 7d1334a..caf95bb 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -12,6 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+java_library_host {
+ name: "tradefed-test-protos",
+ srcs: ["res/**/*.proto"],
+ libs: [
+ "libprotobuf-java-full",
+ ],
+ proto: {
+ include_dirs: ["external/protobuf/src"],
+ type: "full",
+ }
+}
+
tradefed_java_library_host {
name: "tradefed-tests",
defaults: ["tradefed_errorprone_defaults"],
@@ -30,6 +42,7 @@
"easymock",
"objenesis",
"mockito",
+ "tradefed-test-protos",
],
libs: [
"tradefed",
diff --git a/tests/res/proto/proto_util_test.proto b/tests/res/proto/proto_util_test.proto
new file mode 100644
index 0000000..a593326
--- /dev/null
+++ b/tests/res/proto/proto_util_test.proto
@@ -0,0 +1,38 @@
+// Copyright (C) 2020 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.
+
+syntax = "proto3";
+
+option java_package = "com.android.tradefed.util.test";
+option java_outer_classname = "ProtoUtilTestProto";
+
+// A proto for com.android.tradefed.util.ProtoUtilTest.
+message TestMessage {
+ string string_field = 1;
+ int64 int_field = 2;
+ repeated string repeated_string_field = 3;
+
+ message SubMessage {
+ int64 int_field = 1;
+ repeated string repeated_string_field = 2;
+ }
+
+ SubMessage message_field = 4;
+ repeated SubMessage repeated_message_field = 5;
+
+ oneof oneof_field {
+ string oneof_string_field = 6;
+ SubMessage oneof_message_field = 7;
+ }
+}
diff --git a/tests/res/testconfigs/yaml/not-target-preparer.tf_yaml b/tests/res/testconfigs/yaml/not-target-preparer.tf_yaml
new file mode 100644
index 0000000..610b637
--- /dev/null
+++ b/tests/res/testconfigs/yaml/not-target-preparer.tf_yaml
@@ -0,0 +1,16 @@
+description: "Human friendly description of the test"
+
+# pre_setup_action will run before the dependencies installation
+pre_setup_action:
+ - action:
+ name: "com.android.tradefed.testtype.AndroidJUnitTest"
+ options:
+ - package: "android.package"
+
+dependencies:
+
+tests:
+ - test:
+ name: "com.android.tradefed.testtype.AndroidJUnitTest"
+ options:
+ - package: "android.package"
diff --git a/tests/res/testconfigs/yaml/test-config.tf_yaml b/tests/res/testconfigs/yaml/test-config.tf_yaml
index eb89137..2df8a06 100644
--- a/tests/res/testconfigs/yaml/test-config.tf_yaml
+++ b/tests/res/testconfigs/yaml/test-config.tf_yaml
@@ -1,11 +1,29 @@
description: "Human friendly description of the test"
+# pre_setup_action will run before the dependencies installation
+pre_setup_action:
+ - action:
+ name: "com.android.tradefed.targetprep.RunCommandTargetPreparer"
+ options:
+ - run-command: "dumpsys value"
+ - action:
+ name: "com.android.tradefed.targetprep.RunCommandTargetPreparer"
+ options:
+ - run-command: "another one"
+
dependencies:
- apks: ["test.apk", "test2.apk"]
- apks: ["test1.apk"]
- files: ["file1.txt", "file2.txt"]
- device_files: {"tobepushed.txt": "/sdcard", "tobepushed2.txt": "/sdcard/"}
+# post_setup_action will run after the dependencies installation
+post_setup_action:
+ - action:
+ name: "com.android.tradefed.targetprep.RunCommandTargetPreparer"
+ options:
+ - run-command: "dumpsys value2"
+
tests:
- test:
name: "com.android.tradefed.testtype.AndroidJUnitTest"
diff --git a/tests/res/util/partial_zip.zip b/tests/res/util/partial_zip.zip
index 1f50fef..4fb046e 100644
--- a/tests/res/util/partial_zip.zip
+++ b/tests/res/util/partial_zip.zip
Binary files differ
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 832cc950..bbfbb0b 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -89,6 +89,7 @@
import com.android.tradefed.device.cloud.GceSshTunnelMonitorTest;
import com.android.tradefed.device.cloud.ManagedRemoteDeviceTest;
import com.android.tradefed.device.cloud.NestedRemoteDeviceTest;
+import com.android.tradefed.device.cloud.RemoteAndroidVirtualDeviceTest;
import com.android.tradefed.device.cloud.RemoteFileUtilTest;
import com.android.tradefed.device.contentprovider.ContentProviderHandlerTest;
import com.android.tradefed.device.helper.TelephonyHelperTest;
@@ -96,31 +97,22 @@
import com.android.tradefed.device.metric.AtraceRunMetricCollectorTest;
import com.android.tradefed.device.metric.AutoLogCollectorTest;
import com.android.tradefed.device.metric.BaseDeviceMetricCollectorTest;
-import com.android.tradefed.device.metric.BuddyInfoMetricCollectorTest;
-import com.android.tradefed.device.metric.BugreportzMetricCollectorTest;
import com.android.tradefed.device.metric.BugreportzOnFailureCollectorTest;
+import com.android.tradefed.device.metric.ClangCodeCoverageCollectorTest;
import com.android.tradefed.device.metric.DebugHostLogOnFailureCollectorTest;
import com.android.tradefed.device.metric.DeviceMetricDataTest;
-import com.android.tradefed.device.metric.DumpHeapCollectorTest;
import com.android.tradefed.device.metric.FilePullerDeviceMetricCollectorTest;
import com.android.tradefed.device.metric.FilePullerLogCollectorTest;
-import com.android.tradefed.device.metric.GraphicsStatsMetricCollectorTest;
+import com.android.tradefed.device.metric.GcovCodeCoverageCollectorTest;
import com.android.tradefed.device.metric.HostStatsdMetricCollectorTest;
import com.android.tradefed.device.metric.IncidentReportCollectorTest;
-import com.android.tradefed.device.metric.IonHeapInfoMetricCollectorTest;
+import com.android.tradefed.device.metric.JavaCodeCoverageCollectorTest;
import com.android.tradefed.device.metric.LogcatOnFailureCollectorTest;
import com.android.tradefed.device.metric.LogcatTimingMetricCollectorTest;
-import com.android.tradefed.device.metric.MemInfoMetricCollectorTest;
-import com.android.tradefed.device.metric.PagetypeInfoMetricCollectorTest;
import com.android.tradefed.device.metric.PerfettoPullerMetricCollectorTest;
-import com.android.tradefed.device.metric.ProcessMaxMemoryCollectorTest;
import com.android.tradefed.device.metric.RebootReasonCollectorTest;
import com.android.tradefed.device.metric.RuntimeRestartCollectorTest;
-import com.android.tradefed.device.metric.ScheduleMultipleDeviceMetricCollectorTest;
-import com.android.tradefed.device.metric.ScheduledDeviceMetricCollectorTest;
import com.android.tradefed.device.metric.ScreenshotOnFailureCollectorTest;
-import com.android.tradefed.device.metric.TemperatureCollectorTest;
-import com.android.tradefed.device.metric.TraceMetricCollectorTest;
import com.android.tradefed.device.recovery.BatteryUnavailableDeviceRecoveryTest;
import com.android.tradefed.device.recovery.RunConfigDeviceRecoveryTest;
import com.android.tradefed.device.recovery.UsbResetMultiDeviceRecoveryTest;
@@ -155,6 +147,7 @@
import com.android.tradefed.log.LogRegistryTest;
import com.android.tradefed.log.SimpleFileLoggerTest;
import com.android.tradefed.log.TerribleFailureEmailHandlerTest;
+import com.android.tradefed.monitoring.LabResourceDeviceMonitorTest;
import com.android.tradefed.postprocessor.AggregatePostProcessorTest;
import com.android.tradefed.postprocessor.AveragePostProcessorTest;
import com.android.tradefed.postprocessor.BasePostProcessorTest;
@@ -243,6 +236,8 @@
import com.android.tradefed.targetprep.RestartSystemServerTargetPreparerTest;
import com.android.tradefed.targetprep.RootTargetPreparerTest;
import com.android.tradefed.targetprep.RunCommandTargetPreparerTest;
+import com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparerTest;
+import com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparerTest;
import com.android.tradefed.targetprep.RunHostCommandTargetPreparerTest;
import com.android.tradefed.targetprep.RunHostScriptTargetPreparerTest;
import com.android.tradefed.targetprep.StopServicesSetupTest;
@@ -258,8 +253,8 @@
import com.android.tradefed.targetprep.multi.MixImageZipPreparerTest;
import com.android.tradefed.targetprep.suite.SuiteApkInstallerTest;
import com.android.tradefed.testtype.AndroidJUnitTestTest;
+import com.android.tradefed.testtype.ArtGTestTest;
import com.android.tradefed.testtype.ArtRunTestTest;
-import com.android.tradefed.testtype.ClangCodeCoverageListenerTest;
import com.android.tradefed.testtype.DeviceBatteryLevelCheckerTest;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunnerTest;
import com.android.tradefed.testtype.DeviceSuiteTest;
@@ -280,10 +275,8 @@
import com.android.tradefed.testtype.InstrumentationSerialTestTest;
import com.android.tradefed.testtype.InstrumentationTestTest;
import com.android.tradefed.testtype.JarHostTestTest;
-import com.android.tradefed.testtype.JavaCodeCoverageListenerTest;
import com.android.tradefed.testtype.NativeBenchmarkTestParserTest;
import com.android.tradefed.testtype.NativeBenchmarkTestTest;
-import com.android.tradefed.testtype.NativeCodeCoverageListenerTest;
import com.android.tradefed.testtype.NativeStressTestParserTest;
import com.android.tradefed.testtype.NativeStressTestTest;
import com.android.tradefed.testtype.NoisyDryRunTestTest;
@@ -335,6 +328,7 @@
import com.android.tradefed.util.AaptParserTest;
import com.android.tradefed.util.AbiFormatterTest;
import com.android.tradefed.util.AbiUtilsTest;
+import com.android.tradefed.util.AdbRootElevatorTest;
import com.android.tradefed.util.AppVersionFetcherTest;
import com.android.tradefed.util.ArrayUtilTest;
import com.android.tradefed.util.BluetoothUtilsTest;
@@ -364,7 +358,9 @@
import com.android.tradefed.util.NativeCodeCoverageFlusherTest;
import com.android.tradefed.util.PairTest;
import com.android.tradefed.util.PropertyChangerTest;
+import com.android.tradefed.util.ProtoUtilTest;
import com.android.tradefed.util.PsParserTest;
+import com.android.tradefed.util.PythonVirtualenvHelperTest;
import com.android.tradefed.util.QuotationAwareTokenizerTest;
import com.android.tradefed.util.RegexTrieTest;
import com.android.tradefed.util.RemoteZipTest;
@@ -378,6 +374,7 @@
import com.android.tradefed.util.SimpleStatsTest;
import com.android.tradefed.util.SizeLimitedOutputStreamTest;
import com.android.tradefed.util.Sl4aBluetoothUtilTest;
+import com.android.tradefed.util.SparseImageUtilTest;
import com.android.tradefed.util.StreamUtilTest;
import com.android.tradefed.util.StringEscapeUtilsTest;
import com.android.tradefed.util.StringUtilTest;
@@ -518,7 +515,7 @@
GceSshTunnelMonitorTest.class,
ManagedRemoteDeviceTest.class,
NestedRemoteDeviceTest.class,
- RemoteAndroidDeviceTest.class,
+ RemoteAndroidVirtualDeviceTest.class,
RemoteFileUtilTest.class,
// device.contentprovider
@@ -532,31 +529,22 @@
AtraceRunMetricCollectorTest.class,
AutoLogCollectorTest.class,
BaseDeviceMetricCollectorTest.class,
- BuddyInfoMetricCollectorTest.class,
- BugreportzMetricCollectorTest.class,
BugreportzOnFailureCollectorTest.class,
+ ClangCodeCoverageCollectorTest.class,
DebugHostLogOnFailureCollectorTest.class,
DeviceMetricDataTest.class,
- DumpHeapCollectorTest.class,
FilePullerDeviceMetricCollectorTest.class,
FilePullerLogCollectorTest.class,
- GraphicsStatsMetricCollectorTest.class,
+ GcovCodeCoverageCollectorTest.class,
IncidentReportCollectorTest.class,
- IonHeapInfoMetricCollectorTest.class,
+ JavaCodeCoverageCollectorTest.class,
LogcatOnFailureCollectorTest.class,
LogcatTimingMetricCollectorTest.class,
- MemInfoMetricCollectorTest.class,
- PagetypeInfoMetricCollectorTest.class,
PerfettoPullerMetricCollectorTest.class,
- ProcessMaxMemoryCollectorTest.class,
RebootReasonCollectorTest.class,
RuntimeRestartCollectorTest.class,
- ScheduledDeviceMetricCollectorTest.class,
- ScheduleMultipleDeviceMetricCollectorTest.class,
ScreenshotOnFailureCollectorTest.class,
HostStatsdMetricCollectorTest.class,
- TemperatureCollectorTest.class,
- TraceMetricCollectorTest.class,
// device.recovery
BatteryUnavailableDeviceRecoveryTest.class,
@@ -580,7 +568,6 @@
InvocationContextTest.class,
InvocationExecutionTest.class,
RemoteInvocationExecutionTest.class,
- SandboxedInvocationExecutionTest.class,
ShardListenerTest.class,
ShardMainResultForwarderTest.class,
TestInvocationMultiTest.class,
@@ -604,7 +591,6 @@
// invoker.sandbox
ParentSandboxInvocationExecutionTest.class,
- SandboxedInvocationExecutionTest.class,
// lite
DryRunnerTest.class,
@@ -648,7 +634,6 @@
MultiFailureDescriptionTest.class,
SnapshotInputStreamSourceTest.class,
SubprocessResultsReporterTest.class,
- TestDescriptionTest.class,
TestFailureEmailResultReporterTest.class,
PassingTestFileReporterTest.class,
TestDescriptionTest.class,
@@ -708,6 +693,8 @@
RunCommandTargetPreparerTest.class,
RunHostCommandTargetPreparerTest.class,
RunHostScriptTargetPreparerTest.class,
+ RunOnSecondaryUserTargetPreparerTest.class,
+ RunOnWorkProfileTargetPreparerTest.class,
StopServicesSetupTest.class,
SystemUpdaterDeviceFlasherTest.class,
TargetSetupErrorTest.class,
@@ -751,8 +738,8 @@
// testtype
AndroidJUnitTestTest.class,
+ ArtGTestTest.class,
ArtRunTestTest.class,
- ClangCodeCoverageListenerTest.class,
CoverageMeasurementForwarderTest.class,
DeviceBatteryLevelCheckerTest.class,
DeviceJUnit4ClassRunnerTest.class,
@@ -774,10 +761,8 @@
InstrumentationFileTestTest.class,
InstrumentationTestTest.class,
JarHostTestTest.class,
- JavaCodeCoverageListenerTest.class,
NativeBenchmarkTestParserTest.class,
NativeBenchmarkTestTest.class,
- NativeCodeCoverageListenerTest.class,
NativeStressTestParserTest.class,
NativeStressTestTest.class,
NoisyDryRunTestTest.class,
@@ -848,6 +833,7 @@
AaptParserTest.class,
AbiFormatterTest.class,
AbiUtilsTest.class,
+ AdbRootElevatorTest.class,
AppVersionFetcherTest.class,
ArrayUtilTest.class,
BluetoothUtilsTest.class,
@@ -877,7 +863,9 @@
MergedZipEntryCollectionTest.class,
NativeCodeCoverageFlusherTest.class,
PairTest.class,
+ ProtoUtilTest.class,
PsParserTest.class,
+ PythonVirtualenvHelperTest.class,
QuotationAwareTokenizerTest.class,
RegexTrieTest.class,
RemoteZipTest.class,
@@ -891,6 +879,7 @@
SimpleStatsTest.class,
SizeLimitedOutputStreamTest.class,
Sl4aBluetoothUtilTest.class,
+ SparseImageUtilTest.class,
StreamUtilTest.class,
StringEscapeUtilsTest.class,
StringUtilTest.class,
@@ -935,6 +924,9 @@
// util/testmapping
TestInfoTest.class,
TestMappingTest.class,
+
+ // monitoring
+ LabResourceDeviceMonitorTest.class,
})
public class UnitTests {
// empty of purpose
diff --git a/tests/src/com/android/tradefed/build/DeviceBuildDescriptorFuncTest.java b/tests/src/com/android/tradefed/build/DeviceBuildDescriptorFuncTest.java
index 4c180d6..f6d653d 100644
--- a/tests/src/com/android/tradefed/build/DeviceBuildDescriptorFuncTest.java
+++ b/tests/src/com/android/tradefed/build/DeviceBuildDescriptorFuncTest.java
@@ -20,8 +20,8 @@
/**
* Functional tests for {@link DeviceBuildDescriptor}.
- * <p/>
- * Sanity checks data can be retrieved off device.
+ *
+ * <p>Validity checks data can be retrieved off device.
*/
public class DeviceBuildDescriptorFuncTest extends DeviceTestCase {
diff --git a/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java b/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java
index 0d3ffa1..86248bd 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.cluster;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -29,17 +30,21 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.metrics.proto.MetricMeasurement;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.util.FileUtil;
+import org.hamcrest.CoreMatchers;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@@ -52,13 +57,15 @@
public class ClusterCommandLauncherFuncTest {
private static final String LEGACY_TRADEFED_JAR = "/testdata/tradefed-prebuilt-cts-8.0_r21.jar";
- private static final String LEGACY_TRADEFED_COMMAND =
- "host --null-device --class com.android.tradefed.device.DeviceDiagTest";
+ private static final String LEGACY_TRADEFED_COMMAND = "fake.xml --null-device --run testRun PF";
+ private static final String LEGACY_TRADEFED_COMMAND_FOR_INVOCATION_FAILURE =
+ "fake.xml --null-device --fail-invocation-with-cause cause";
private File mRootDir;
private IConfiguration mConfiguration;
private IInvocationContext mInvocationContext;
private OptionSetter mOptionSetter;
+ @Mock private TestInformation mTestInformation;
@Spy private ClusterCommandLauncher mLauncher;
@Mock private ITestInvocationListener mListener;
@@ -81,22 +88,51 @@
FileUtil.recursiveDelete(mRootDir);
}
- @Ignore
@Test
public void testRun_withLegacyTradefed()
throws IOException, ConfigurationException, DeviceNotAvailableException {
File tfJar = new File(mRootDir, "tradefed.jar");
FileUtil.writeToFile(getClass().getResourceAsStream(LEGACY_TRADEFED_JAR), tfJar);
+ FileUtil.writeToFile(
+ getClass().getResourceAsStream("/config/tf/fake.xml"),
+ new File(mRootDir, "fake.xml"));
mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", mRootDir.getAbsolutePath());
mOptionSetter.setOptionValue("cluster:use-subprocess-reporting", "true");
mOptionSetter.setOptionValue("cluster:command-line", LEGACY_TRADEFED_COMMAND);
- mLauncher.run(mListener);
+ mLauncher.run(mTestInformation, mListener);
+ InOrder inOrder = Mockito.inOrder(mListener);
HashMap<String, MetricMeasurement.Metric> emptyMap = new HashMap<>();
- verify(mListener).testRunStarted(anyString(), anyInt(), anyInt(), anyLong());
- verify(mListener).testStarted(any(TestDescription.class), anyLong());
- verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap));
- verify(mListener).testRunEnded(anyLong(), eq(emptyMap));
+ inOrder.verify(mListener).testRunStarted(eq("testRun"), anyInt(), anyInt(), anyLong());
+ inOrder.verify(mListener).testStarted(any(TestDescription.class), anyLong());
+ inOrder.verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap));
+ inOrder.verify(mListener).testStarted(any(TestDescription.class), anyLong());
+ inOrder.verify(mListener).testFailed(any(TestDescription.class), anyString());
+ inOrder.verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap));
+ inOrder.verify(mListener).testRunEnded(anyLong(), eq(emptyMap));
+ }
+
+ @Test
+ public void testRun_withLegacyTradefed_invocationFailed()
+ throws IOException, ConfigurationException, DeviceNotAvailableException {
+ File tfJar = new File(mRootDir, "tradefed.jar");
+ FileUtil.writeToFile(getClass().getResourceAsStream(LEGACY_TRADEFED_JAR), tfJar);
+ FileUtil.writeToFile(
+ getClass().getResourceAsStream("/config/tf/fake.xml"),
+ new File(mRootDir, "fake.xml"));
+ mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", mRootDir.getAbsolutePath());
+ mOptionSetter.setOptionValue("cluster:use-subprocess-reporting", "true");
+ mOptionSetter.setOptionValue(
+ "cluster:command-line", LEGACY_TRADEFED_COMMAND_FOR_INVOCATION_FAILURE);
+
+ try {
+ mLauncher.run(mTestInformation, mListener);
+ fail("SubprocessCommandException should be thrown");
+ } catch (SubprocessCommandException e) {
+ Assert.assertThat(e.getCause().getMessage(), CoreMatchers.containsString("cause"));
+ }
+
+ verify(mListener).invocationFailed(any(Throwable.class));
}
}
diff --git a/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherTest.java b/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherTest.java
index c6dbe31..0dd755f 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherTest.java
@@ -15,7 +15,6 @@
*/
package com.android.tradefed.cluster;
-import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import com.android.tradefed.config.Configuration;
@@ -26,6 +25,7 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.CommandResult;
@@ -54,9 +54,11 @@
public class ClusterCommandLauncherTest {
private static final String DEVICE_SERIAL = "device_serial";
+ private static final String COMMAND = "host";
private IRunUtil mMockRunUtil;
private SubprocessTestResultsParser mMockSubprocessTestResultsParser;
+ private TestInformation mMockTestInformation;
private ITestInvocationListener mMockListener;
private ITestDevice mMockTestDevice;
private File mTfPath;
@@ -79,6 +81,7 @@
public void setUp() throws Exception {
mMockRunUtil = Mockito.mock(IRunUtil.class);
mMockSubprocessTestResultsParser = Mockito.mock(SubprocessTestResultsParser.class);
+ mMockTestInformation = Mockito.mock(TestInformation.class);
mMockListener = Mockito.mock(ITestInvocationListener.class);
mMockTestDevice = Mockito.mock(ITestDevice.class);
Mockito.doReturn(DEVICE_SERIAL).when(mMockTestDevice).getSerialNumber();
@@ -116,14 +119,15 @@
tfJar.getName(), mTfPath.getName(), mTfLibDir.getName());
final List<String> jars = new ArrayList<>();
jars.add(tfJar.getAbsolutePath());
+ jars.add(String.format("%s/", mTfPath));
jars.add(String.format("%s/*", mTfPath));
+ jars.add(String.format("%s/", mTfLibDir));
jars.add(String.format("%s/*", mTfLibDir));
final String classPath = ArrayUtil.join(":", jars);
mOptionSetter.setOptionValue("cluster:jvm-option", "-Xmx1g");
mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", tfPathValue);
mOptionSetter.setOptionValue("cluster:java-property", "FOO", "${TF_WORK_DIR}/foo");
- mOptionSetter.setOptionValue("cluster:original-command-line", "original-command-line");
- mOptionSetter.setOptionValue("cluster:command-line", "command-line");
+ mOptionSetter.setOptionValue("cluster:command-line", COMMAND);
final String expandedTfPathValue =
String.format(
"%s:%s:%s",
@@ -139,7 +143,7 @@
.thenReturn(mockCommandResult);
Mockito.when(mLauncher.getRunUtil()).thenReturn(mMockRunUtil);
- mLauncher.run(mMockListener);
+ mLauncher.run(mMockTestInformation, mMockListener);
Mockito.verify(mMockRunUtil, Mockito.times(2)).setWorkingDir(mRootDir);
Mockito.verify(mMockRunUtil).unsetEnvVariable("TF_GLOBAL_CONFIG");
@@ -158,25 +162,24 @@
"-Xmx1g",
"-DFOO=" + mRootDir.getAbsolutePath() + "/foo",
"com.android.tradefed.command.CommandRunner",
- "command-line",
+ COMMAND,
"--serial",
DEVICE_SERIAL
}));
- assertTrue(new File(mRootDir, "_original-command-line.xml").exists());
}
@Test
public void testRun_withSetupScripts()
throws DeviceNotAvailableException, ConfigurationException {
mInvocationContext.addAllocatedDevice("foo", mMockTestDevice);
- final String classpath = String.format("%s/*", mTfPath);
+ final String classpath = String.format("%s/:%s/*", mTfPath, mTfPath);
mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", mTfPath.getAbsolutePath());
mOptionSetter.setOptionValue("cluster:env-var", "FOO", "foo");
mOptionSetter.setOptionValue("cluster:env-var", "BAR", "bar");
mOptionSetter.setOptionValue("cluster:env-var", "ZZZ", "zzz");
mOptionSetter.setOptionValue("cluster:setup-script", "foo bar zzz");
mOptionSetter.setOptionValue("cluster:setup-script", "${FOO} ${BAR} ${ZZZ}");
- mOptionSetter.setOptionValue("cluster:command-line", "command-line");
+ mOptionSetter.setOptionValue("cluster:command-line", COMMAND);
final CommandResult mockCommandResult = new CommandResult(CommandStatus.SUCCESS);
when(mMockRunUtil.runTimedCmd(
Mockito.anyLong(),
@@ -186,7 +189,7 @@
.thenReturn(mockCommandResult);
Mockito.when(mLauncher.getRunUtil()).thenReturn(mMockRunUtil);
- mLauncher.run(mMockListener);
+ mLauncher.run(mMockTestInformation, mMockListener);
Mockito.verify(mMockRunUtil, Mockito.times(2)).setWorkingDir(mRootDir);
Mockito.verify(mMockRunUtil).unsetEnvVariable("TF_GLOBAL_CONFIG");
@@ -212,7 +215,7 @@
"-cp",
classpath,
"com.android.tradefed.command.CommandRunner",
- "command-line",
+ COMMAND,
"--serial",
DEVICE_SERIAL
}));
@@ -222,9 +225,23 @@
public void testRun_withUseSubprocessReporting()
throws DeviceNotAvailableException, ConfigurationException, IOException {
mInvocationContext.addAllocatedDevice("foo", mMockTestDevice);
- final String classpath = String.format("%s/*", mTfPath);
- mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", mTfPath.getAbsolutePath());
- mOptionSetter.setOptionValue("cluster:command-line", "command-line");
+ // We need to use the real TF path to allow ClusterCommandLauncher to find a config.
+ final File tfPath =
+ new File(
+ ClusterCommandLauncher.class
+ .getProtectionDomain()
+ .getCodeSource()
+ .getLocation()
+ .getPath());
+ String classpath;
+ if (tfPath.isDirectory()) {
+ classpath =
+ String.format("%s/:%s/*", tfPath.getAbsolutePath(), tfPath.getAbsolutePath());
+ } else {
+ classpath = tfPath.getAbsolutePath();
+ }
+ mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", tfPath.getAbsolutePath());
+ mOptionSetter.setOptionValue("cluster:command-line", COMMAND);
mOptionSetter.setOptionValue("cluster:use-subprocess-reporting", "true");
when(mMockSubprocessTestResultsParser.getSocketServerPort()).thenReturn(123);
Mockito.when(
@@ -238,18 +255,16 @@
Mockito.<OutputStream>any(),
Mockito.<String[]>any()))
.thenReturn(mockCommandResult);
- final File subprocessReporterConfig = new File(mRootDir, "_command-line.xml");
Mockito.when(mLauncher.getRunUtil()).thenReturn(mMockRunUtil);
- mLauncher.run(mMockListener);
+ mLauncher.run(mMockTestInformation, mMockListener);
String subprocessJar =
FileUtil.findFile(mRootDir, "subprocess-results-reporter.jar").getAbsolutePath();
- assertTrue(subprocessReporterConfig.exists());
Mockito.verify(mMockRunUtil, Mockito.times(2)).setWorkingDir(mRootDir);
Mockito.verify(mMockRunUtil).unsetEnvVariable("TF_GLOBAL_CONFIG");
Mockito.verify(mMockRunUtil).setEnvVariable("TF_WORK_DIR", mRootDir.getAbsolutePath());
- Mockito.verify(mMockRunUtil).setEnvVariable("TF_PATH", mTfPath.getAbsolutePath());
+ Mockito.verify(mMockRunUtil).setEnvVariable("TF_PATH", tfPath.getAbsolutePath());
Mockito.verify(mMockRunUtil).unsetEnvVariable("TF_GLOBAL_CONFIG");
Mockito.verify(mMockRunUtil)
.runTimedCmd(
@@ -262,7 +277,7 @@
"-cp",
subprocessJar + ":" + classpath,
"com.android.tradefed.command.CommandRunner",
- subprocessReporterConfig.getName(),
+ COMMAND,
"--serial",
DEVICE_SERIAL,
}));
diff --git a/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java b/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
index 0a20930..adfc487 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
@@ -521,6 +521,23 @@
new String[] {CMD_LINE, "--serial", "deviceSerial"}, getExecCommandArgs());
}
+ /**
+ * If a unique device serial (one with a hostname prefix) is specified for a command task,
+ * convert it to a local device serial before appending it.
+ */
+ @Test
+ public void testExecCommandWithVirtualDeviceSerial() {
+ List<ClusterCommand> cmds = new ArrayList<>();
+ ClusterCommand cmd = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE);
+ cmd.setTargetDeviceSerials(
+ ArrayUtil.list(ClusterHostUtil.getHostName() + ":emulator-5554"));
+ cmds.add(cmd);
+ mScheduler.execCommands(cmds);
+ assertEquals(CMD_LINE, cmds.get(0).getCommandLine());
+ assertArrayEquals(
+ new String[] {CMD_LINE, "--serial", "emulator-5554"}, getExecCommandArgs());
+ }
+
/** Multiple serials specified for a command task. */
@Test
public void testExecCommandWithMultipleSerials() {
@@ -828,6 +845,70 @@
handler.invocationInitiated(context);
}
+ @Test
+ public void testInvocationEventHandler_withSubprocessCommandException() {
+ ClusterCommand mockCommand = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE);
+ IInvocationContext context = new InvocationContext();
+ ITestDevice mockTestDevice = EasyMock.createMock(ITestDevice.class);
+ EasyMock.expect(mockTestDevice.getSerialNumber()).andReturn(DEVICE_SERIAL);
+ EasyMock.expect(mockTestDevice.getIDevice()).andReturn(new StubDevice(DEVICE_SERIAL));
+ context.addAllocatedDevice("", mockTestDevice);
+ IBuildInfo mockBuildInfo = EasyMock.createMock(IBuildInfo.class);
+ context.addDeviceBuildInfo("", mockBuildInfo);
+ ClusterCommandScheduler.InvocationEventHandler handler =
+ mScheduler.new InvocationEventHandler(mockCommand);
+ mMockClusterOptions.setCollectEarlyTestSummary(true);
+
+ mMockEventUploader.postEvent(
+ checkClusterCommandEvent(ClusterCommandEvent.Type.InvocationInitiated));
+ mMockEventUploader.flush();
+ mMockEventUploader.postEvent(
+ checkClusterCommandEvent(ClusterCommandEvent.Type.InvocationStarted));
+ mMockEventUploader.flush();
+ mMockEventUploader.postEvent(
+ checkClusterCommandEvent(ClusterCommandEvent.Type.TestRunInProgress));
+ EasyMock.expectLastCall().anyTimes();
+ mMockEventUploader.postEvent(
+ checkClusterCommandEvent(ClusterCommandEvent.Type.InvocationEnded));
+ mMockEventUploader.flush();
+ Capture<ClusterCommandEvent> capture = new Capture<>();
+ mMockEventUploader.postEvent(EasyMock.capture(capture));
+ mMockEventUploader.flush();
+
+ EasyMock.replay(mMockEventUploader, mockBuildInfo, mockTestDevice);
+ handler.invocationInitiated(context);
+ List<TestSummary> summaries = new ArrayList<>();
+ summaries.add(
+ new TestSummary(new TestSummary.TypedString("http://uri", TestSummary.Type.URI)));
+ handler.putEarlySummary(summaries);
+ handler.putSummary(summaries);
+ handler.invocationStarted(context);
+ handler.invocationFailed(
+ new SubprocessCommandException(
+ "error_message", new Throwable("subprocess_command_error_message")));
+ handler.invocationEnded(100L);
+ context.addAllocatedDevice(DEVICE_SERIAL, mockTestDevice);
+ Map<ITestDevice, FreeDeviceState> releaseMap = new HashMap<>();
+ releaseMap.put(mockTestDevice, FreeDeviceState.AVAILABLE);
+ handler.invocationComplete(context, releaseMap);
+ EasyMock.verify(mMockEventUploader, mockBuildInfo, mockTestDevice);
+ ClusterCommandEvent capturedEvent = capture.getValue();
+ assertTrue(capturedEvent.getType().equals(ClusterCommandEvent.Type.InvocationCompleted));
+ assertTrue(
+ ((String) capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_ERROR))
+ .contains("SubprocessCommandException"));
+ assertEquals(
+ "subprocess_command_error_message",
+ capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_SUBPROCESS_COMMAND_ERROR));
+ assertEquals(
+ "0", capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_FAILED_TEST_COUNT));
+ assertEquals(
+ "0", capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_PASSED_TEST_COUNT));
+ assertEquals(
+ "URI: http://uri\n",
+ capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_SUMMARY));
+ }
+
/**
* Test that when dry-run is used we validate the config and no ConfigurationException gets
* thrown.
@@ -1058,7 +1139,8 @@
List<IDeviceConfiguration> deviceConfigs = config.getDeviceConfig();
assertEquals(cmd.getTargetDeviceSerials().size(), deviceConfigs.size());
for (int i = 0; i < cmd.getTargetDeviceSerials().size(); i++) {
- String serial = cmd.getTargetDeviceSerials().get(i);
+ String serial =
+ ClusterHostUtil.getLocalDeviceSerial(cmd.getTargetDeviceSerials().get(i));
Collection<String> serials =
deviceConfigs.get(i).getDeviceRequirements().getSerials(null);
assertTrue(serials.size() == 1 && serials.contains(serial));
@@ -1165,6 +1247,44 @@
}
}
+ /** Tests an execution of a managed cluster command. */
+ @Test
+ public void testExecManagedClusterCommand_virtualDeviceTest() throws Exception {
+ File workDir = null;
+ try {
+ ClusterCommand cmd = createMockManagedCommand(1);
+ cmd.setTargetDeviceSerials(
+ ArrayUtil.list(ClusterHostUtil.getHostName() + ":emulator-5554"));
+ workDir = new File(System.getProperty("java.io.tmpdir"), cmd.getAttemptId());
+ TestEnvironment testEnvironment = createMockTestEnvironment();
+ List<TestResource> testResources = createMockTestResources();
+ TestContext testContext = new TestContext();
+ mMockClusterClient = Mockito.spy(mMockClusterClient);
+ Mockito.doReturn(testEnvironment)
+ .when(mMockClusterClient)
+ .getTestEnvironment(REQUEST_ID);
+ Mockito.doReturn(testResources).when(mMockClusterClient).getTestResources(REQUEST_ID);
+ Mockito.doReturn(testContext)
+ .when(mMockClusterClient)
+ .getTestContext(REQUEST_ID, COMMAND_ID);
+ InvocationEventHandler invocationEventHandler =
+ mScheduler.new InvocationEventHandler(cmd);
+
+ mScheduler.execManagedClusterCommand(cmd, invocationEventHandler);
+
+ String[] args = getExecCommandArgs();
+ assertTrue(args.length > 0);
+ IConfiguration config =
+ ConfigurationFactory.getInstance().createConfigurationFromArgs(args);
+ verifyConfig(config, cmd, testEnvironment, testResources, workDir);
+ } finally {
+ if (workDir != null) {
+ // Clean up work directory
+ FileUtil.recursiveDelete(workDir);
+ }
+ }
+ }
+
/** Tests an execution of a managed cluster command for multiple devices. */
@Test
public void testExecManagedClusterCommand_multiDeviceTest() throws Exception {
@@ -1421,8 +1541,8 @@
// command status is CANCELED
mMockClusterClient = Mockito.mock(IClusterClient.class, RETURNS_DEEP_STUBS);
- Mockito.when(mMockClusterClient.getCommandState(any(), any()))
- .thenReturn(ClusterCommand.State.CANCELED);
+ Mockito.when(mMockClusterClient.getCommandStatus(any(), any()))
+ .thenReturn(new ClusterCommandStatus(ClusterCommand.State.CANCELED, "Reason"));
// not stopped if check is disabled
mMockClusterOptions.setCheckCommandState(false);
@@ -1434,7 +1554,7 @@
heartbeat.run();
assertTrue(scheduler.wasStopInvocationCalled());
- Mockito.verify(mMockClusterClient, Mockito.times(1)).getCommandState(any(), any());
+ Mockito.verify(mMockClusterClient, Mockito.times(1)).getCommandStatus(any(), any());
}
/** Tests whether the heartbeat can determine the invocationId to stop. */
@@ -1450,8 +1570,8 @@
// command status is CANCELED
mMockClusterClient = Mockito.mock(IClusterClient.class, RETURNS_DEEP_STUBS);
- Mockito.when(mMockClusterClient.getCommandState(any(), any()))
- .thenReturn(ClusterCommand.State.CANCELED);
+ Mockito.when(mMockClusterClient.getCommandStatus(any(), any()))
+ .thenReturn(new ClusterCommandStatus(ClusterCommand.State.CANCELED, "Reason"));
// not stopped without invocation context
heartbeat.run();
@@ -1473,7 +1593,7 @@
heartbeat.run();
assertTrue(scheduler.wasStopInvocationCalled());
- Mockito.verify(mMockClusterClient, Mockito.times(4)).getCommandState(any(), any());
+ Mockito.verify(mMockClusterClient, Mockito.times(4)).getCommandStatus(any(), any());
}
/** Tests whether the heartbeat can determine the cluster command state. */
diff --git a/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java b/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java
index fc98304..d0ba372 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java
@@ -100,6 +100,24 @@
Assert.assertEquals("cluster1", hostEvent.getClusterId());
Assert.assertEquals(Arrays.asList("cluster2", "cluster3"), hostEvent.getNextClusterIds());
Assert.assertEquals("lab1", hostEvent.getLabName());
+ Assert.assertEquals("", hostEvent.getData().get("label"));
+ }
+
+ @Test
+ public void testLabel() throws Exception {
+ mClusterOptionSetter.setOptionValue("cluster:label", "label1");
+ mClusterOptionSetter.setOptionValue("cluster:label", "label2");
+ Capture<ClusterHostEvent> capture = new Capture<>();
+ mHostEventUploader.postEvent(EasyMock.capture(capture));
+ mHostEventUploader.flush();
+ EasyMock.replay(mHostEventUploader);
+ mEventDispatcher.dispatch();
+ EasyMock.verify(mHostEventUploader);
+ ClusterHostEvent hostEvent = capture.getValue();
+ Assert.assertEquals("cluster1", hostEvent.getClusterId());
+ Assert.assertEquals(Arrays.asList("cluster2", "cluster3"), hostEvent.getNextClusterIds());
+ Assert.assertEquals("lab1", hostEvent.getLabName());
+ Assert.assertEquals("label1,label2", hostEvent.getData().get("label"));
}
void setOptions() throws Exception {
diff --git a/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java b/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java
index e2c10bd..a7ff29c 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java
@@ -25,6 +25,8 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.easymock.EasyMock;
import org.junit.Assert;
@@ -37,6 +39,7 @@
public class ClusterHostUtilTest {
private static final String DEVICE_SERIAL = "serial";
+ private static final String EMULATOR_SERIAL = "emulator-5554";
@Test
public void testIsIpPort() {
@@ -287,4 +290,71 @@
"simOperator");
Assert.assertEquals("product", ClusterHostUtil.getRunTarget(device, format, null));
}
+
+ @Test
+ public void testGetRunTarget_withStubDevice() {
+ final String hostname = ClusterHostUtil.getHostName();
+ // with a stub device.
+ DeviceDescriptor device =
+ new DeviceDescriptor(
+ DEVICE_SERIAL,
+ true,
+ DeviceAllocationState.Available,
+ "product",
+ "productVariant",
+ "sdkVersion",
+ "buildId",
+ "batteryLevel");
+ Assert.assertEquals(
+ hostname + ":" + DEVICE_SERIAL,
+ ClusterHostUtil.getRunTarget(device, "{SERIAL}", null));
+ }
+
+ @Test
+ public void testGetRunTarget_withEmulator() {
+ final String hostname = ClusterHostUtil.getHostName();
+ // with a stub device.
+ DeviceDescriptor device =
+ new DeviceDescriptor(
+ EMULATOR_SERIAL,
+ false,
+ DeviceAllocationState.Available,
+ "product",
+ "productVariant",
+ "sdkVersion",
+ "buildId",
+ "batteryLevel");
+ Assert.assertEquals(
+ hostname + ":" + EMULATOR_SERIAL,
+ ClusterHostUtil.getRunTarget(device, "{SERIAL}", null));
+ }
+
+ @Test
+ public void testGetRunTarget_withEmptyDeviceSerial() {
+ final String hostname = ClusterHostUtil.getHostName();
+ // with a stub device.
+ DeviceDescriptor device =
+ new DeviceDescriptor(
+ "",
+ false,
+ DeviceAllocationState.Available,
+ "product",
+ "productVariant",
+ "sdkVersion",
+ "buildId",
+ "batteryLevel");
+ Assert.assertEquals(
+ hostname + ":" + ClusterHostUtil.NULL_DEVICE_SERIAL_PLACEHOLDER,
+ ClusterHostUtil.getRunTarget(device, "{SERIAL}", null));
+ }
+
+ @Test
+ public void testGetHostIpAddress() {
+ final String hostIp = ClusterHostUtil.getHostIpAddress();
+ Assert.assertNotEquals(hostIp, "127.0.0.1");
+ Pattern pattern =
+ Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}" + "|UNKNOWN");
+ Matcher matcher = pattern.matcher(hostIp);
+ Assert.assertTrue("host ip format not match: " + hostIp, matcher.matches());
+ }
}
diff --git a/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java b/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java
index e588940..67622f3 100644
--- a/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java
+++ b/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java
@@ -17,6 +17,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import com.android.tradefed.result.LegacySubprocessResultsReporter;
import com.android.tradefed.util.FileUtil;
@@ -43,11 +44,13 @@
private static final String REPORTER_CLASS = LegacySubprocessResultsReporter.class.getName();
private SubprocessConfigBuilder mConfigBuilder;
+ private String mClasspath;
private File mWorkDir;
@Before
public void setUp() throws IOException {
mConfigBuilder = new SubprocessConfigBuilder();
+ mClasspath = System.getProperty("java.class.path");
mWorkDir = FileUtil.createTempDir("tfjar");
}
@@ -58,9 +61,10 @@
@Test
public void testCreateWrapperConfig() throws Exception {
- String oriConfigName = "testConfig";
+ String oriConfigName = "host";
String reporterPort = "1024";
mConfigBuilder
+ .setClasspath(mClasspath)
.setWorkingDir(mWorkDir)
.setOriginalConfig(oriConfigName)
.setPort(reporterPort);
@@ -69,19 +73,33 @@
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(config);
- verifyWrapperXml(doc, oriConfigName, reporterPort);
+ verifyWrapperXml(doc, reporterPort);
}
- private void verifyWrapperXml(Document doc, String oriConfigName, String reporterPort) {
- NodeList inc = doc.getElementsByTagName("include");
- assertEquals(1, inc.getLength());
- String incName = ((Element) inc.item(0)).getAttribute("name");
- assertEquals(oriConfigName, incName);
- NodeList reporter = doc.getElementsByTagName("result_reporter");
- assertEquals(1, reporter.getLength());
- String reporterClass = ((Element) reporter.item(0)).getAttribute("class");
+ @Test
+ public void testCreateWrapperConfig_forCommandWithSlashes() throws Exception {
+ String oriConfigName = "util/timewaster";
+ String reporterPort = "1024";
+ mConfigBuilder
+ .setClasspath(mClasspath)
+ .setWorkingDir(mWorkDir)
+ .setOriginalConfig(oriConfigName)
+ .setPort(reporterPort);
+ File config = mConfigBuilder.build();
+ assertNotNull(config);
+ DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+ Document doc = dBuilder.parse(config);
+ verifyWrapperXml(doc, reporterPort);
+ }
+
+ private void verifyWrapperXml(Document doc, String reporterPort) {
+ NodeList reporters = doc.getElementsByTagName("result_reporter");
+ assertTrue(0 < reporters.getLength());
+ Element reporter = (Element) reporters.item(reporters.getLength() - 1);
+ String reporterClass = reporter.getAttribute("class");
assertEquals(REPORTER_CLASS, reporterClass);
- NodeList option = ((Element) reporter.item(0)).getElementsByTagName("option");
+ NodeList option = reporter.getElementsByTagName("option");
assertEquals(1, option.getLength());
String optionName = ((Element) option.item(0)).getAttribute("name");
assertEquals("subprocess-report-port", optionName);
diff --git a/tests/src/com/android/tradefed/cluster/SubprocessReportingHelperTest.java b/tests/src/com/android/tradefed/cluster/SubprocessReportingHelperTest.java
index 0dd72b0..ed2a779 100644
--- a/tests/src/com/android/tradefed/cluster/SubprocessReportingHelperTest.java
+++ b/tests/src/com/android/tradefed/cluster/SubprocessReportingHelperTest.java
@@ -34,11 +34,13 @@
public class SubprocessReportingHelperTest {
private SubprocessReportingHelper mHelper;
private File mWorkDir;
+ private String mClasspath;
@Before
public void setUp() throws IOException {
- mHelper = new SubprocessReportingHelper();
mWorkDir = FileUtil.createTempDir("tfjar");
+ mClasspath = System.getProperty("java.class.path");
+ mHelper = new SubprocessReportingHelper("host", mClasspath, mWorkDir, null);
}
@After
@@ -48,7 +50,7 @@
@Test
public void testCreateSubprocessReporterJar() throws IOException {
- File jar = mHelper.createSubprocessReporterJar(mWorkDir);
+ File jar = mHelper.buildSubprocessReporterJar();
assertNotNull(jar);
File extractedJar = ZipUtil2.extractZipToTemp(jar, "tmp-jar");
try {
@@ -56,24 +58,9 @@
assertNotNull(FileUtil.findFile(extractedJar, "SubprocessTestResultsParser.class"));
assertNotNull(FileUtil.findFile(extractedJar, "SubprocessEventHelper.class"));
assertNotNull(FileUtil.findFile(extractedJar, "SubprocessResultsReporter.class"));
+ assertNotNull(FileUtil.findFile(extractedJar, "host.xml"));
} finally {
FileUtil.recursiveDelete(extractedJar);
}
}
-
- @Test
- public void testGetNewCommandLine() throws IOException {
- String oldCommandLine = "cts arg1 arg2 arg3";
- String newCommandLine = mHelper.buildNewCommandConfig(oldCommandLine, "1024", mWorkDir);
- assertNotNull(FileUtil.findFile(mWorkDir, "_cts.xml"));
- assertEquals("_cts.xml arg1 arg2 arg3", newCommandLine);
- }
-
- @Test
- public void testGetNewCommandLine_withSlashes() throws IOException {
- String oldCommandLine = "foo/bar/cts arg1 arg2 arg3";
- String newCommandLine = mHelper.buildNewCommandConfig(oldCommandLine, "1024", mWorkDir);
- assertNotNull(FileUtil.findFile(mWorkDir, "_foo\\$bar\\$cts.xml"));
- assertEquals("_foo$bar$cts.xml arg1 arg2 arg3", newCommandLine);
- }
}
diff --git a/tests/src/com/android/tradefed/command/CommandSchedulerTest.java b/tests/src/com/android/tradefed/command/CommandSchedulerTest.java
index 793ee11..7373e33 100644
--- a/tests/src/com/android/tradefed/command/CommandSchedulerTest.java
+++ b/tests/src/com/android/tradefed/command/CommandSchedulerTest.java
@@ -365,12 +365,14 @@
mScheduler =
new TestableCommandScheduler() {
@Override
- Map<String, ITestDevice> allocateDevices(
+ DeviceAllocationResult allocateDevices(
IConfiguration config, IDeviceManager manager) {
+ DeviceAllocationResult results = new DeviceAllocationResult();
Map<String, ITestDevice> allocated = new HashMap<>();
((MockDeviceManager) manager).addDevice(mockDevice);
allocated.put("device", ((MockDeviceManager) manager).allocateDevice());
- return allocated;
+ results.addAllocatedDevices(allocated);
+ return results;
}
};
replayMocks(mockDevice, mockListener);
@@ -1094,8 +1096,10 @@
mMockConfiguration.validateOptions();
replayMocks();
mScheduler.start();
- Map<String, ITestDevice> devices = mScheduler.allocateDevices(
- mMockConfiguration, mMockManager);
+ DeviceAllocationResult results =
+ mScheduler.allocateDevices(mMockConfiguration, mMockManager);
+ assertTrue(results.wasAllocationSuccessful());
+ Map<String, ITestDevice> devices = results.getAllocatedDevices();
assertEquals(1, devices.size());
mScheduler.shutdown();
}
@@ -1120,8 +1124,10 @@
mMockConfiguration.setDeviceConfigList(EasyMock.anyObject());
replayMocks();
mScheduler.start();
- Map<String, ITestDevice> devices =
+ DeviceAllocationResult results =
mScheduler.allocateDevices(mMockConfiguration, mMockManager);
+ assertTrue(results.wasAllocationSuccessful());
+ Map<String, ITestDevice> devices = results.getAllocatedDevices();
// With replicated setup, all devices get allocated.
assertEquals(3, devices.size());
mScheduler.shutdown();
@@ -1147,8 +1153,10 @@
mMockConfiguration.validateOptions();
replayMocks();
mScheduler.start();
- Map<String, ITestDevice> devices = mScheduler.allocateDevices(
- mMockConfiguration, mMockManager);
+ DeviceAllocationResult results =
+ mScheduler.allocateDevices(mMockConfiguration, mMockManager);
+ assertTrue(results.wasAllocationSuccessful());
+ Map<String, ITestDevice> devices = results.getAllocatedDevices();
assertEquals(2, devices.size());
assertEquals(0, mMockManager.getQueueOfAvailableDeviceSize());
mScheduler.shutdown();
@@ -1166,8 +1174,10 @@
mMockConfiguration.validateOptions();
replayMocks();
mScheduler.start();
- Map<String, ITestDevice> devices = mScheduler.allocateDevices(
- mMockConfiguration, mMockManager);
+ DeviceAllocationResult results =
+ mScheduler.allocateDevices(mMockConfiguration, mMockManager);
+ assertFalse(results.wasAllocationSuccessful());
+ Map<String, ITestDevice> devices = results.getAllocatedDevices();
assertEquals(0, devices.size());
assertEquals(2, mMockManager.getQueueOfAvailableDeviceSize());
mScheduler.shutdown();
@@ -1279,12 +1289,14 @@
mScheduler =
new TestableCommandScheduler() {
@Override
- Map<String, ITestDevice> allocateDevices(
+ DeviceAllocationResult allocateDevices(
IConfiguration config, IDeviceManager manager) {
+ DeviceAllocationResult results = new DeviceAllocationResult();
Map<String, ITestDevice> allocated = new HashMap<>();
((MockDeviceManager) manager).addDevice(mockDevice);
allocated.put("device", ((MockDeviceManager) manager).allocateDevice());
- return allocated;
+ results.addAllocatedDevices(allocated);
+ return results;
}
};
diff --git a/tests/src/com/android/tradefed/config/ConfigurationDescriptorTest.java b/tests/src/com/android/tradefed/config/ConfigurationDescriptorTest.java
index 1a6c1b5..067fc2c 100644
--- a/tests/src/com/android/tradefed/config/ConfigurationDescriptorTest.java
+++ b/tests/src/com/android/tradefed/config/ConfigurationDescriptorTest.java
@@ -62,7 +62,7 @@
fail("Should have thrown an exception.");
} catch (OptionNotAllowedException expected) {
assertEquals(
- "Option test-suite-tag cannot be specified via command line. "
+ "Option 'test-suite-tag' cannot be specified via command line. "
+ "Only in the configuration xml.",
expected.getMessage());
}
diff --git a/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java b/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java
index ffa3255..87e60b7 100644
--- a/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java
+++ b/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java
@@ -90,7 +90,7 @@
};
}
- /** Sanity test to ensure all config names on classpath are loadable */
+ /** Initial test to ensure all config names on classpath are loadable */
public void testLoadAllConfigs() throws Exception {
ConfigurationFactory spyFactory = Mockito.spy(mRealFactory);
Mockito.doReturn(new HashSet<String>()).when(spyFactory).getConfigNamesFromTestCases(null);
@@ -135,9 +135,7 @@
}
}
- /**
- * Sanity test to ensure all configs on classpath can be fully loaded and parsed
- */
+ /** Initial test to ensure all configs on classpath can be fully loaded and parsed */
public void testLoadAndPrintAllConfigs() throws ConfigurationException {
ConfigurationFactory spyFactory = Mockito.spy(mRealFactory);
Mockito.doReturn(new HashSet<String>()).when(spyFactory).getConfigNamesFromTestCases(null);
diff --git a/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java b/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java
index eded0ac..97730b4 100644
--- a/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java
+++ b/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java
@@ -387,7 +387,7 @@
call.add(callableTask);
devices.add(Mockito.mock(ITestDevice.class));
}
- ParallelDeviceExecutor<Set<File>> executor = new ParallelDeviceExecutor<>(devices);
+ ParallelDeviceExecutor<Set<File>> executor = new ParallelDeviceExecutor<>(devices.size());
List<Set<File>> downloadedFile = null;
downloadedFile = executor.invokeAll(call, 1, TimeUnit.MINUTES);
boolean oneMustBeNonEmpty = false;
@@ -604,7 +604,7 @@
IRemoteFileResolver actual = loader.load(NullFileResolver.PROTOCOL, ImmutableMap.of());
- assertThat(actual).isSameAs(expected);
+ assertThat(actual).isSameInstanceAs(expected);
}
@Test
@@ -616,7 +616,7 @@
IRemoteFileResolver resolver1 = loader1.load(NullFileResolver.PROTOCOL, ImmutableMap.of());
IRemoteFileResolver resolver2 = loader2.load(NullFileResolver.PROTOCOL, ImmutableMap.of());
- assertThat(resolver1).isNotSameAs(resolver2);
+ assertThat(resolver1).isNotSameInstanceAs(resolver2);
}
@Test
@@ -968,15 +968,9 @@
public static final class DuplicateNullFileResolver extends NullFileResolver {}
private static final Correspondence<File, File> FILE_PATH_EQUIVALENCE =
- new Correspondence<File, File>() {
- @Override
- public boolean compare(File actual, File expected) {
- return expected.getAbsolutePath().equals(actual.getAbsolutePath());
- }
-
- @Override
- public String toString() {
- return "is equivalent to";
- }
- };
+ Correspondence.from(
+ (File actual, File expected) -> {
+ return expected.getAbsolutePath().equals(actual.getAbsolutePath());
+ },
+ "is equivalent to");
}
diff --git a/tests/src/com/android/tradefed/config/remote/GcsRemoteFileResolverTest.java b/tests/src/com/android/tradefed/config/remote/GcsRemoteFileResolverTest.java
index 0fcdaee..3f59fc0 100644
--- a/tests/src/com/android/tradefed/config/remote/GcsRemoteFileResolverTest.java
+++ b/tests/src/com/android/tradefed/config/remote/GcsRemoteFileResolverTest.java
@@ -75,9 +75,7 @@
mResolver.resolveRemoteFiles(new File("gs:/fake/file"), new HashMap<>());
fail("Should have thrown an exception.");
} catch (BuildRetrievalError expected) {
- assertEquals(
- "Failed to download gs:/fake/file due to: download failure",
- expected.getMessage());
+ assertEquals("download failure", expected.getMessage());
}
Mockito.verify(mMockHelper).fetchTestResource("gs:/fake/file");
diff --git a/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java b/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java
index 6a1c081..4784443 100644
--- a/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java
+++ b/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java
@@ -19,15 +19,18 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.tradefed.build.DependenciesResolver;
import com.android.tradefed.build.StubBuildProvider;
import com.android.tradefed.config.ConfigurationDef;
+import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.suite.SuiteResultReporter;
import com.android.tradefed.targetprep.ITargetPreparer;
import com.android.tradefed.targetprep.PushFilePreparer;
+import com.android.tradefed.targetprep.RunCommandTargetPreparer;
import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
import com.android.tradefed.testtype.AndroidJUnitTest;
@@ -45,6 +48,8 @@
public class ConfigurationYamlParserTest {
private static final String YAML_TEST_CONFIG_1 = "/testconfigs/yaml/test-config.tf_yaml";
+ private static final String YAML_TEST_CONFIG_2 =
+ "/testconfigs/yaml/not-target-preparer.tf_yaml";
private ConfigurationYamlParser mParser;
private ConfigurationDef mConfigDef;
@@ -84,14 +89,19 @@
// Dependencies
// apk dependencies
- assertEquals(2, config.getTargetPreparers().size());
- ITargetPreparer installApk = config.getTargetPreparers().get(0);
+ assertEquals(5, config.getTargetPreparers().size());
+ ITargetPreparer preCommandRunner = config.getTargetPreparers().get(0);
+ assertTrue(preCommandRunner instanceof RunCommandTargetPreparer);
+ ITargetPreparer preCommandRunner2 = config.getTargetPreparers().get(1);
+ assertTrue(preCommandRunner2 instanceof RunCommandTargetPreparer);
+
+ ITargetPreparer installApk = config.getTargetPreparers().get(2);
assertTrue(installApk instanceof SuiteApkInstaller);
assertThat(((SuiteApkInstaller) installApk).getTestsFileName())
.containsExactly(
new File("test.apk"), new File("test2.apk"), new File("test1.apk"));
// device file dependencies
- ITargetPreparer pushFile = config.getTargetPreparers().get(1);
+ ITargetPreparer pushFile = config.getTargetPreparers().get(3);
assertTrue(pushFile instanceof PushFilePreparer);
assertThat(((PushFilePreparer) pushFile).getPushSpecs(null))
.containsExactly(
@@ -99,6 +109,8 @@
new File("tobepushed2.txt"),
"/sdcard",
new File("tobepushed.txt"));
+ ITargetPreparer postCommandRunner = config.getTargetPreparers().get(4);
+ assertTrue(postCommandRunner instanceof RunCommandTargetPreparer);
// Result reporters
List<ITestInvocationListener> listeners = config.getTestInvocationListeners();
assertTrue(listeners.get(0) instanceof SuiteResultReporter);
@@ -123,14 +135,14 @@
// Dependencies
// apk dependencies
- assertEquals(2, config.getTargetPreparers().size());
- ITargetPreparer installApk = config.getTargetPreparers().get(0);
+ assertEquals(5, config.getTargetPreparers().size());
+ ITargetPreparer installApk = config.getTargetPreparers().get(2);
assertTrue(installApk instanceof SuiteApkInstaller);
assertThat(((SuiteApkInstaller) installApk).getTestsFileName())
.containsExactly(
new File("test.apk"), new File("test2.apk"), new File("test1.apk"));
// device file dependencies
- ITargetPreparer pushFile = config.getTargetPreparers().get(1);
+ ITargetPreparer pushFile = config.getTargetPreparers().get(3);
assertTrue(pushFile instanceof PushFilePreparer);
assertThat(((PushFilePreparer) pushFile).getPushSpecs(null))
.containsExactly(
@@ -145,6 +157,19 @@
}
}
+ @Test
+ public void testParseConfig_notTargetPreparer() throws Exception {
+ try (InputStream res = readFromRes(YAML_TEST_CONFIG_2)) {
+ mParser.parse(mConfigDef, "source", res, false);
+ try {
+ mConfigDef.createConfiguration();
+ fail("Should have thrown an exception");
+ } catch (ConfigurationException expected) {
+
+ }
+ }
+ }
+
private InputStream readFromRes(String resourceFile) {
return getClass().getResourceAsStream(resourceFile);
}
diff --git a/tests/src/com/android/tradefed/device/TestDeviceTest.java b/tests/src/com/android/tradefed/device/TestDeviceTest.java
index 10ca9a5..7308581 100644
--- a/tests/src/com/android/tradefed/device/TestDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/TestDeviceTest.java
@@ -4211,11 +4211,11 @@
Assert.assertEquals(3000, testImage.data.length);
byte[] result = mTestDevice.compressRawImage(testImage, "PNG", true);
// Size after compressing can vary a bit depending of the JDK
- if (result.length != 107 && result.length != 117 && result.length != 139) {
+ if (result.length != 107 && result.length != 117) {
fail(
String.format(
"Should have compress the length as expected, got %s, "
- + "expected 107 or 117 or 139",
+ + "expected 107 or 117",
result.length));
}
@@ -4742,64 +4742,43 @@
}
/**
- * Test {@link TestDevice#doesFileExist(String)} when the file exists on an sdcard from another
- * user.
+ * Test {@link TestDevice#doesFileExist(String)} using content provider when the file is in
+ * external storage path.
*/
public void testDoesFileExists_sdcard() throws Exception {
- mTestDevice =
- new TestableTestDevice() {
- @Override
- public int getCurrentUser()
- throws DeviceNotAvailableException, DeviceRuntimeException {
- return 10;
- }
- };
- injectShellResponse("ls \"/storage/emulated/10/file\"", "file");
+ mTestDevice = createTestDevice();
+
+ TestableTestDevice spy = (TestableTestDevice) Mockito.spy(mTestDevice);
+ ContentProviderHandler cp = Mockito.mock(ContentProviderHandler.class);
+ doReturn(cp).when(spy).getContentProvider();
+
+ final String fakeFile = "/sdcard/file";
+ final String targetFilePath = "/storage/emulated/10/file";
+
+ doReturn("").when(spy).executeShellCommand(Mockito.contains("content query --user 10"));
+
EasyMock.replay(mMockIDevice);
- assertTrue(mTestDevice.doesFileExist("/sdcard/file"));
+ spy.doesFileExist(fakeFile);
EasyMock.verify(mMockIDevice);
+
+ verify(spy, times(1)).getContentProvider();
+ verify(cp, times(1)).doesFileExist(targetFilePath);
}
/** Push a file using the content provider. */
public void testPushFile_contentProvider() throws Exception {
- mTestDevice =
- new TestableTestDevice() {
- @Override
- public int getApiLevel() throws DeviceNotAvailableException {
- return 29;
- }
-
- @Override
- public int getCurrentUser()
- throws DeviceNotAvailableException, DeviceRuntimeException {
- return 10;
- }
-
- @Override
- public boolean isPackageInstalled(String packageName, String userId)
- throws DeviceNotAvailableException {
- return false;
- }
- };
+ mTestDevice = createTestDevice();
TestableTestDevice spy = (TestableTestDevice) Mockito.spy(mTestDevice);
+ setupContentProvider(spy);
+
final String fakeRemotePath = "/sdcard/";
File tmpFile = FileUtil.createTempFile("push", ".test");
- doReturn(null)
- .when(spy)
- .installPackage(Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean());
- CommandResult setLegacy = new CommandResult(CommandStatus.SUCCESS);
- doReturn(setLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops set"));
-
- CommandResult getLegacy = new CommandResult(CommandStatus.SUCCESS);
- getLegacy.setStdout("LEGACY_STORAGE: allow");
- doReturn(getLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops get"));
CommandResult writeContent = new CommandResult(CommandStatus.SUCCESS);
writeContent.setStdout("");
doReturn(writeContent)
.when(spy)
.executeShellV2Command(Mockito.contains("content write"), (File) Mockito.any());
- doReturn(null).when(spy).uninstallPackage(Mockito.eq("android.tradefed.contentprovider"));
EasyMock.replay(mMockIDevice);
try {
boolean res = spy.pushFile(tmpFile, fakeRemotePath);
@@ -4820,37 +4799,12 @@
/** Push a file using the content provider. */
public void testPushFile_contentProvider_notFound() throws Exception {
- mTestDevice =
- new TestableTestDevice() {
- @Override
- public int getApiLevel() throws DeviceNotAvailableException {
- return 29;
- }
-
- @Override
- public int getCurrentUser()
- throws DeviceNotAvailableException, DeviceRuntimeException {
- return 10;
- }
-
- @Override
- public boolean isPackageInstalled(String packageName, String userId)
- throws DeviceNotAvailableException {
- return false;
- }
- };
+ mTestDevice = createTestDevice();
TestableTestDevice spy = (TestableTestDevice) Mockito.spy(mTestDevice);
+ setupContentProvider(spy);
+
final String fakeRemotePath = "/sdcard/";
File tmpFile = FileUtil.createTempFile("push", ".test");
- doReturn(null)
- .when(spy)
- .installPackage(Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean());
- CommandResult setLegacy = new CommandResult(CommandStatus.SUCCESS);
- doReturn(setLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops set"));
-
- CommandResult getLegacy = new CommandResult(CommandStatus.SUCCESS);
- getLegacy.setStdout("LEGACY_STORAGE: allow");
- doReturn(getLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops get"));
CommandResult writeContent = new CommandResult(CommandStatus.SUCCESS);
writeContent.setStdout("");
@@ -4896,4 +4850,38 @@
EasyMock.eq(property)))
.andReturn(stubResult);
}
+
+ private void setupContentProvider(TestableTestDevice spy) throws Exception {
+ doReturn(null)
+ .when(spy)
+ .installPackage(Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean());
+ CommandResult setLegacy = new CommandResult(CommandStatus.SUCCESS);
+ doReturn(setLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops set"));
+
+ CommandResult getLegacy = new CommandResult(CommandStatus.SUCCESS);
+ getLegacy.setStdout("LEGACY_STORAGE: allow");
+ doReturn(getLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops get"));
+
+ doReturn(null).when(spy).uninstallPackage(Mockito.eq("android.tradefed.contentprovider"));
+ }
+
+ private TestableTestDevice createTestDevice() {
+ return new TestableTestDevice() {
+ @Override
+ public int getApiLevel() throws DeviceNotAvailableException {
+ return 29;
+ }
+
+ @Override
+ public int getCurrentUser() throws DeviceNotAvailableException, DeviceRuntimeException {
+ return 10;
+ }
+
+ @Override
+ public boolean isPackageInstalled(String packageName, String userId)
+ throws DeviceNotAvailableException {
+ return false;
+ }
+ };
+ }
}
diff --git a/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java b/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java
index e8d0219..18109d1 100644
--- a/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java
@@ -58,6 +58,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -323,6 +324,32 @@
/** Test {@link RemoteAndroidVirtualDevice#postInvocationTearDown(Throwable)}. */
@Test
public void testPostInvocationTearDown() throws Exception {
+ mTestDevice =
+ new TestableRemoteAndroidVirtualDevice() {
+ @Override
+ protected IRunUtil getRunUtil() {
+ return mMockRunUtil;
+ }
+
+ @Override
+ void createGceSshMonitor(
+ ITestDevice device,
+ IBuildInfo buildInfo,
+ HostAndPort hostAndPort,
+ TestDeviceOptions deviceOptions) {
+ // ignore
+ }
+
+ @Override
+ GceManager getGceHandler() {
+ return mGceHandler;
+ }
+
+ @Override
+ public DeviceDescriptor getDeviceDescriptor() {
+ return null;
+ }
+ };
mTestDevice.setTestLogger(mTestLogger);
EasyMock.expect(mMockStateMonitor.waitForDeviceNotAvailable(EasyMock.anyLong()))
.andReturn(true);
@@ -803,4 +830,83 @@
}
verifyMocks();
}
+
+ /**
+ * Run powerwash() but GceAvdInfo = null, RemoteAndroidVirtualDevice choose to throw exception.
+ */
+ @Test
+ public void testPowerwashNoAvdInfo() throws Exception {
+ final String expectedException = "Can not get GCE AVD Info. launch GCE first? [ : ]";
+ EasyMock.replay(mMockRunUtil, mMockIDevice);
+ try {
+ mTestDevice.powerwashGce();
+ fail("Should have thrown an exception");
+ } catch (TargetSetupError expected) {
+ assertEquals(expectedException, expected.getMessage());
+ }
+ EasyMock.verify(mMockRunUtil, mMockIDevice);
+ }
+
+ /** Test powerwash GCE command */
+ @Test
+ public void testPowerwashGce() throws Exception {
+ mTestDevice =
+ new TestableRemoteAndroidVirtualDevice() {
+ @Override
+ public IDevice getIDevice() {
+ return mMockIDevice;
+ }
+
+ @Override
+ GceManager getGceHandler() {
+ return mGceHandler;
+ }
+
+ @Override
+ void createGceSshMonitor(
+ ITestDevice device,
+ IBuildInfo buildInfo,
+ HostAndPort hostAndPort,
+ TestDeviceOptions deviceOptions) {
+ // ignore
+ }
+ };
+ String instanceUser = "user1";
+ IBuildInfo mMockBuildInfo = EasyMock.createMock(IBuildInfo.class);
+ OptionSetter setter = new OptionSetter(mTestDevice.getOptions());
+ setter.setOptionValue("instance-user", instanceUser);
+ String powerwashCommand = String.format("/home/%s/bin/powerwash_cvd", instanceUser);
+ String avdConnectHost = String.format("%s@127.0.0.1", instanceUser);
+ GceAvdInfo gceAvd =
+ new GceAvdInfo(
+ instanceUser, HostAndPort.fromHost("127.0.0.1"), null, GceStatus.SUCCESS);
+ doReturn(gceAvd).when(mGceHandler).startGce(null);
+ OutputStream stdout = null;
+ OutputStream stderr = null;
+ CommandResult powerwashCmdResult = new CommandResult(CommandStatus.SUCCESS);
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(),
+ EasyMock.eq(stdout),
+ EasyMock.eq(stderr),
+ EasyMock.eq("ssh"),
+ EasyMock.eq("-o"),
+ EasyMock.eq("UserKnownHostsFile=/dev/null"),
+ EasyMock.eq("-o"),
+ EasyMock.eq("StrictHostKeyChecking=no"),
+ EasyMock.eq("-o"),
+ EasyMock.eq("ServerAliveInterval=10"),
+ EasyMock.eq("-i"),
+ EasyMock.anyObject(),
+ EasyMock.eq(avdConnectHost),
+ EasyMock.eq(powerwashCommand)))
+ .andReturn(powerwashCmdResult);
+ EasyMock.expect(mMockStateMonitor.waitForDeviceAvailable(EasyMock.anyLong()))
+ .andReturn(mMockIDevice);
+ EasyMock.replay(mMockRunUtil, mMockIDevice);
+ // Launch GCE before powerwash.
+ mTestDevice.launchGce(mMockBuildInfo);
+ mTestDevice.powerwashGce();
+ EasyMock.verify(mMockRunUtil, mMockIDevice);
+ }
}
diff --git a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
index b0da7d4..0570a59 100644
--- a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
+++ b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.util.CommandResult;
@@ -369,6 +370,36 @@
espacedUrl);
}
+ /** Test {@link ContentProviderHandler#doesFileExist(String)}. */
+ @Test
+ public void testDoesFileExist() throws Exception {
+ String devicePath = "path/somewhere/file.txt";
+
+ when(mMockDevice.getCurrentUser()).thenReturn(99);
+ when(mMockDevice.executeShellCommand(
+ "content query --user 99 --uri "
+ + ContentProviderHandler.createEscapedContentUri(devicePath)))
+ .thenReturn("");
+
+ assertTrue(mProvider.doesFileExist(devicePath));
+ }
+
+ /**
+ * Test {@link ContentProviderHandler#doesFileExist(String)} returns false when 'adb shell
+ * content query' returns no results.
+ */
+ @Test
+ public void testDoesFileExist_NotExists() throws Exception {
+ String devicePath = "path/somewhere/";
+
+ when(mMockDevice.getCurrentUser()).thenReturn(99);
+ when(mMockDevice.executeShellCommand(
+ "content query --user 99 --uri "
+ + ContentProviderHandler.createEscapedContentUri(devicePath)))
+ .thenReturn("No result found.\n");
+ assertFalse(mProvider.doesFileExist(devicePath));
+ }
+
@Test
public void testParseQueryResultRow() {
String row =
diff --git a/tests/src/com/android/tradefed/device/metric/BuddyInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/BuddyInfoMetricCollectorTest.java
deleted file mode 100644
index 27bf498..0000000
--- a/tests/src/com/android/tradefed/device/metric/BuddyInfoMetricCollectorTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link BuddyInfoMetricCollector}. */
-// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class BuddyInfoMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice device;
-
- @Spy BuddyInfoMetricCollector mBuddyInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mBuddyInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class));
-
- doReturn(new File("unusable-index-1"))
- .when(mBuddyInfoMetricCollector)
- .saveProcessOutput(any(ITestDevice.class), anyString(), anyString());
-
- doReturn(tempFolder.newFolder()).when(mBuddyInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- mBuddyInfoMetricCollector.collect(device, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("unusable-index-1"), eq(LogDataType.TEXT), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/BugreportzMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/BugreportzMetricCollectorTest.java
deleted file mode 100644
index 6b19eb4..0000000
--- a/tests/src/com/android/tradefed/device/metric/BugreportzMetricCollectorTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import java.util.Arrays;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-/** Unit tests for {@link BugreportzMetricCollector}. */
-@RunWith(JUnit4.class)
-public class BugreportzMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestDevice mTestDevice;
-
- @Mock ITestInvocationListener mForwarder;
-
- @Spy BugreportzMetricCollector mBugreportzMetricCollector;
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- doReturn(Arrays.asList(mTestDevice)).when(mContext).getDevices();
-
- when(mTestDevice.logBugreport(anyString(), any(ITestInvocationListener.class)))
- .thenReturn(true);
-
- mBugreportzMetricCollector.init(mContext, mForwarder);
-
- when(mBugreportzMetricCollector.getFileSuffix()).thenReturn("1");
- }
-
- /** Tests successful collection of bugreport. */
- @Test
- public void testCollect_success() throws Exception {
- when(mTestDevice.logBugreport("bugreportz-1", mForwarder)).thenReturn(true);
-
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- mBugreportzMetricCollector.collect(mTestDevice, runData);
-
- verify(mTestDevice).logBugreport(eq("bugreport-1"), eq(mForwarder));
- }
-}
diff --git a/tests/src/com/android/tradefed/testtype/ClangCodeCoverageListenerTest.java b/tests/src/com/android/tradefed/device/metric/ClangCodeCoverageCollectorTest.java
similarity index 84%
rename from tests/src/com/android/tradefed/testtype/ClangCodeCoverageListenerTest.java
rename to tests/src/com/android/tradefed/device/metric/ClangCodeCoverageCollectorTest.java
index 69f60fd..c354f03 100644
--- a/tests/src/com/android/tradefed/testtype/ClangCodeCoverageListenerTest.java
+++ b/tests/src/com/android/tradefed/device/metric/ClangCodeCoverageCollectorTest.java
@@ -14,20 +14,23 @@
* limitations under the License.
*/
-package com.android.tradefed.testtype;
+package com.android.tradefed.device.metric;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IBuildProvider;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.metrics.proto.MetricMeasurement;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
@@ -38,7 +41,6 @@
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
-import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.protobuf.ByteString;
@@ -51,7 +53,9 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
@@ -70,7 +74,7 @@
/** Unit tests for {@link ClangCodeCoverageListener}. */
@RunWith(JUnit4.class)
-public class ClangCodeCoverageListenerTest {
+public class ClangCodeCoverageCollectorTest {
private static final String RUN_NAME = "SomeTest";
private static final int TEST_COUNT = 5;
@@ -86,6 +90,7 @@
@Mock IConfiguration mMockConfiguration;
@Mock IBuildProvider mMockBuildProvider;
@Mock ITestDevice mMockDevice;
+ @Mock IInvocationContext mMockContext;
@Spy CommandArgumentCaptor mCommandArgumentCaptor;
LogFileReader mFakeListener = new LogFileReader();
@@ -95,7 +100,7 @@
OptionSetter mCoverageOptionsSetter = null;
/** Object under test. */
- ClangCodeCoverageListener mListener;
+ ClangCodeCoverageCollector mListener;
@Before
public void setUp() throws Exception {
@@ -111,13 +116,17 @@
doReturn(mMockBuildProvider).when(mMockConfiguration).getBuildProvider();
doReturn(mMockBuildInfo).when(mMockBuildProvider).getBuild();
- mListener = new ClangCodeCoverageListener(mMockDevice, mFakeListener);
+ doReturn(ImmutableList.of(mMockDevice)).when(mMockContext).getDevices();
+
+ mListener = new ClangCodeCoverageCollector();
mListener.setConfiguration(mMockConfiguration);
mListener.setRunUtil(mCommandArgumentCaptor);
}
@Test
public void coverageDisabled_noCoverageLog() {
+ mListener.init(mMockContext, mFakeListener);
+
// Simulate a test run.
mListener.testRunStarted(RUN_NAME, TEST_COUNT);
mListener.testRunEnded(ELAPSED_TIME, mMetrics);
@@ -131,6 +140,8 @@
public void clangCoverageDisabled_noCoverageLog() throws Exception {
mCoverageOptionsSetter.setOptionValue("coverage", "true");
+ mListener.init(mMockContext, mFakeListener);
+
// Simulate a test run.
mListener.testRunStarted(RUN_NAME, TEST_COUNT);
mListener.testRunEnded(ELAPSED_TIME, mMetrics);
@@ -147,17 +158,18 @@
mCoverageOptionsSetter.setOptionValue("coverage-flush", "true");
// Setup mocks.
- doReturn(true).when(mMockDevice).enableAdbRoot();
doReturn(true).when(mMockDevice).isAdbRoot();
doReturn(createTar(ImmutableMap.of())).when(mMockDevice).pullFile(anyString());
// Simulate a test run.
+ mListener.init(mMockContext, mFakeListener);
mListener.testRunStarted(RUN_NAME, TEST_COUNT);
mListener.testRunEnded(ELAPSED_TIME, mMetrics);
mListener.invocationEnded(ELAPSED_TIME);
- // Verify the flush-all-coverage command was called.
- verify(mMockDevice).executeShellCommand("kill -37 -1");
+ // Verify the flush-all-coverage command was called twice - once on init() and once during
+ // the end of the test run.
+ verify(mMockDevice, times(2)).executeShellCommand("kill -37 -1");
}
@Test
@@ -166,7 +178,7 @@
mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "CLANG");
// Setup mocks.
- doReturn(true).when(mMockDevice).enableAdbRoot();
+ doReturn(true).when(mMockDevice).isAdbRoot();
File tarGz =
createTar(
ImmutableMap.of(
@@ -179,6 +191,7 @@
doReturn(createProfileToolZip()).when(mMockBuildInfo).getFile(anyString());
// Simulate a test run.
+ mListener.init(mMockContext, mFakeListener);
mListener.testRunStarted(RUN_NAME, TEST_COUNT);
mListener.testRunEnded(ELAPSED_TIME, mMetrics);
mListener.invocationEnded(ELAPSED_TIME);
@@ -203,7 +216,7 @@
mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "CLANG");
// Setup mocks.
- doReturn(true).when(mMockDevice).enableAdbRoot();
+ doReturn(true).when(mMockDevice).isAdbRoot();
File tarGz =
createTar(
ImmutableMap.of(
@@ -216,6 +229,7 @@
doReturn(createProfileToolZip()).when(mMockBuildInfo).getFile(anyString());
// Simulate a test run.
+ mListener.init(mMockContext, mFakeListener);
mListener.testRunStarted(RUN_NAME, TEST_COUNT);
mListener.testRunEnded(ELAPSED_TIME, mMetrics);
mListener.invocationEnded(ELAPSED_TIME);
@@ -237,10 +251,11 @@
mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "CLANG");
// Setup mocks.
- doReturn(true).when(mMockDevice).enableAdbRoot();
+ doReturn(true).when(mMockDevice).isAdbRoot();
doReturn(createTar(ImmutableMap.of())).when(mMockDevice).pullFile(anyString());
// Simulate a test run.
+ mListener.init(mMockContext, mFakeListener);
mListener.testRunStarted(RUN_NAME, TEST_COUNT);
mListener.testRunEnded(ELAPSED_TIME, mMetrics);
mListener.invocationEnded(ELAPSED_TIME);
@@ -256,7 +271,7 @@
mCoverageOptionsSetter.setOptionValue("llvm-profdata-path", "/path/to/some/directory");
// Setup mocks.
- doReturn(true).when(mMockDevice).enableAdbRoot();
+ doReturn(true).when(mMockDevice).isAdbRoot();
File tarGz =
createTar(
ImmutableMap.of(
@@ -267,6 +282,7 @@
doReturn(tarGz).when(mMockDevice).pullFile(anyString());
// Simulate a test run.
+ mListener.init(mMockContext, mFakeListener);
mListener.testRunStarted(RUN_NAME, TEST_COUNT);
mListener.testRunEnded(ELAPSED_TIME, mMetrics);
mListener.invocationEnded(ELAPSED_TIME);
@@ -277,12 +293,12 @@
}
@Test
- public void testProfileToolNotFound_throwsException() throws Exception {
+ public void testProfileToolNotFound_noLog() throws Exception {
mCoverageOptionsSetter.setOptionValue("coverage", "true");
mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "CLANG");
// Setup mocks.
- doReturn(true).when(mMockDevice).enableAdbRoot();
+ doReturn(true).when(mMockDevice).isAdbRoot();
File tarGz =
createTar(
ImmutableMap.of(
@@ -293,27 +309,22 @@
doReturn(tarGz).when(mMockDevice).pullFile(anyString());
// Simulate a test run.
- try {
- mListener.testRunStarted(RUN_NAME, TEST_COUNT);
- mListener.testRunEnded(ELAPSED_TIME, mMetrics);
- mListener.invocationEnded(ELAPSED_TIME);
- fail("an exception should have been thrown");
- } catch (VerifyException e) {
- // Expected.
- assertThat(e).hasMessageThat().contains("llvm-profdata");
- }
+ mListener.init(mMockContext, mFakeListener);
+ mListener.testRunStarted(RUN_NAME, TEST_COUNT);
+ mListener.testRunEnded(ELAPSED_TIME, mMetrics);
+ mListener.invocationEnded(ELAPSED_TIME);
// Verify testLog(..) was never called.
assertThat(mFakeListener.getLogs()).isEmpty();
}
@Test
- public void testProfileToolFailed_throwsException() throws Exception {
+ public void testProfileToolFailed_noLog() throws Exception {
mCoverageOptionsSetter.setOptionValue("coverage", "true");
mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "CLANG");
// Setup mocks.
- doReturn(true).when(mMockDevice).enableAdbRoot();
+ doReturn(true).when(mMockDevice).isAdbRoot();
File tarGz =
createTar(
ImmutableMap.of(
@@ -327,20 +338,36 @@
mCommandArgumentCaptor.setResult(CommandStatus.FAILED);
// Simulate a test run.
- try {
- mListener.testRunStarted(RUN_NAME, TEST_COUNT);
- mListener.testRunEnded(ELAPSED_TIME, mMetrics);
- fail("an exception should have been thrown");
- } catch (RuntimeException e) {
- // Expected.
- assertThat(e).hasMessageThat().contains("merge Clang profile data");
- }
+ mListener.init(mMockContext, mFakeListener);
+ mListener.testRunStarted(RUN_NAME, TEST_COUNT);
+ mListener.testRunEnded(ELAPSED_TIME, mMetrics);
mListener.invocationEnded(ELAPSED_TIME);
// Verify testLog(..) was never called.
assertThat(mFakeListener.getLogs()).isEmpty();
}
+ @Test
+ public void testInit_adbRootAndCoverageFlush() throws Exception {
+ mCoverageOptionsSetter.setOptionValue("coverage", "true");
+ mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "CLANG");
+
+ // Setup mocks.
+ when(mMockDevice.isAdbRoot()).thenReturn(false).thenReturn(true);
+ doReturn(true).when(mMockDevice).enableAdbRoot();
+
+ // Call init(...).
+ mListener.init(mMockContext, mFakeListener);
+
+ // Verify.
+ InOrder inOrder = Mockito.inOrder(mMockDevice);
+ inOrder.verify(mMockDevice).isAdbRoot();
+ inOrder.verify(mMockDevice).enableAdbRoot();
+ inOrder.verify(mMockDevice).executeShellCommand("kill -37 -1");
+ inOrder.verify(mMockDevice).executeShellCommand(anyString());
+ inOrder.verify(mMockDevice).disableAdbRoot();
+ }
+
abstract static class CommandArgumentCaptor implements IRunUtil {
private List<String> mCommand;
private CommandResult mResult = new CommandResult(CommandStatus.SUCCESS);
diff --git a/tests/src/com/android/tradefed/device/metric/DumpHeapCollectorTest.java b/tests/src/com/android/tradefed/device/metric/DumpHeapCollectorTest.java
deleted file mode 100644
index 6a9cb56..0000000
--- a/tests/src/com/android/tradefed/device/metric/DumpHeapCollectorTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.util.FileUtil;
-import com.google.common.truth.Truth;
-import java.io.File;
-import java.util.Arrays;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-/** Unit tests for {@link DumpHeapCollector}. */
-@RunWith(JUnit4.class)
-public class DumpHeapCollectorTest {
-
- @Mock private IInvocationContext mContext;
-
- @Mock private ITestInvocationListener mListener;
-
- @Mock private ITestDevice mDevice;
-
- @Rule public TemporaryFolder folder = new TemporaryFolder();
-
- @Spy private DumpHeapCollector mDumpheapCollector;
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- when(mDevice.executeShellCommand("dumpsys meminfo -c | grep camera"))
- .thenReturn("proc,native,camera,21348,800,N/A,e\n");
-
- when(mDevice.executeShellCommand("dumpsys meminfo -c | grep maps"))
- .thenReturn("proc,native,maps,21349,900,N/A,e\n");
-
- when(mDevice.executeShellCommand("am dumpheap camera /data/local/tmp/camera_trigger.hprof"))
- .thenReturn("");
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), any(LogDataType.class), any(InputStreamSource.class));
- }
-
- @Test
- public void testTakeDumpheap_success() throws Exception {
- File mapsDumpheap1 = folder.newFile("maps1");
- File mapsDumpheap2 = folder.newFile("maps2");
-
- doReturn("1").when(mDumpheapCollector).getFileSuffix();
-
- when(mDevice.dumpHeap("maps", "/data/local/tmp/maps_trigger_1.hprof"))
- .thenReturn(mapsDumpheap1)
- .thenReturn(mapsDumpheap2);
-
- String fakeDumpheapOutput =
- "proc,native,maps,21349,900,N/A,e\nproc,native,camera,21350,800,N/A,e\n";
-
- List<File> files =
- mDumpheapCollector.takeDumpheap(mDevice, fakeDumpheapOutput, "maps", 850L);
-
- Truth.assertThat(files).containsExactly(mapsDumpheap1);
- }
-
- @Test
- public void testCollect_success() throws Exception {
- File tempFile1 = folder.newFile();
- File tempFile2 = folder.newFile();
- when(mDevice.dumpHeap(anyString(), anyString()))
- .thenReturn(tempFile1)
- .thenReturn(tempFile2);
-
- OptionSetter options = new OptionSetter(mDumpheapCollector);
-
- options.setOptionValue("dumpheap-thresholds", "camera", "700");
- options.setOptionValue("dumpheap-thresholds", "maps", "800");
-
- mDumpheapCollector.init(mContext, mListener);
-
- mDumpheapCollector.collect(mDevice, null);
-
- ArgumentCaptor<String> dataNameCaptor = ArgumentCaptor.forClass(String.class);
- ArgumentCaptor<LogDataType> dataTypeCaptor = ArgumentCaptor.forClass(LogDataType.class);
- ArgumentCaptor<InputStreamSource> inputCaptor =
- ArgumentCaptor.forClass(InputStreamSource.class);
-
- verify(mListener, times(2))
- .testLog(dataNameCaptor.capture(), dataTypeCaptor.capture(), inputCaptor.capture());
-
- // Assert that the correct filename was sent to testLog.
- Truth.assertThat(dataNameCaptor.getAllValues())
- .containsExactlyElementsIn(
- Arrays.asList(
- FileUtil.getBaseName(tempFile1.getName()),
- FileUtil.getBaseName(tempFile2.getName())));
-
- // Assert that the correct data type was sent to testLog.
- Truth.assertThat(dataTypeCaptor.getAllValues())
- .containsExactlyElementsIn(Arrays.asList(LogDataType.HPROF, LogDataType.HPROF));
- }
-
- @Test
- public void testCollectSuccess_thresholdTooHigh() throws Exception {
- File tempFile1 = folder.newFile();
- File tempFile2 = folder.newFile();
- when(mDevice.pullFile(anyString())).thenReturn(tempFile1).thenReturn(tempFile2);
-
- OptionSetter options = new OptionSetter(mDumpheapCollector);
-
- options.setOptionValue("dumpheap-thresholds", "camera", "7000");
- options.setOptionValue("dumpheap-thresholds", "maps", "8000");
-
- mDumpheapCollector.init(mContext, mListener);
-
- mDumpheapCollector.collect(mDevice, null);
-
- ArgumentCaptor<String> dataNameCaptor = ArgumentCaptor.forClass(String.class);
- ArgumentCaptor<LogDataType> dataTypeCaptor = ArgumentCaptor.forClass(LogDataType.class);
- ArgumentCaptor<InputStreamSource> inputCaptor =
- ArgumentCaptor.forClass(InputStreamSource.class);
-
- verify(mListener, times(0))
- .testLog(dataNameCaptor.capture(), dataTypeCaptor.capture(), inputCaptor.capture());
- }
-
- @Test
- public void testCollectNoError_processNotFound() throws Exception {
- // Make the meminfo dump not contain the heap info of fake_process.
- when(mDevice.executeShellCommand("dumpsys meminfo -c | grep fake_process")).thenReturn("");
-
- OptionSetter options = new OptionSetter(mDumpheapCollector);
- options.setOptionValue("dumpheap-thresholds", "fake_process", "7000");
-
- mDumpheapCollector.init(mContext, mListener);
-
- mDumpheapCollector.collect(mDevice, null);
-
- ArgumentCaptor<String> dataNameCaptor = ArgumentCaptor.forClass(String.class);
- ArgumentCaptor<LogDataType> dataTypeCaptor = ArgumentCaptor.forClass(LogDataType.class);
- ArgumentCaptor<InputStreamSource> inputCaptor =
- ArgumentCaptor.forClass(InputStreamSource.class);
-
- // Verify that no testLog calls were made.
- verify(mListener, times(0))
- .testLog(dataNameCaptor.capture(), dataTypeCaptor.capture(), inputCaptor.capture());
- }
-}
-
diff --git a/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java b/tests/src/com/android/tradefed/device/metric/GcovCodeCoverageCollectorTest.java
similarity index 75%
rename from tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java
rename to tests/src/com/android/tradefed/device/metric/GcovCodeCoverageCollectorTest.java
index 6d7e90e..6ce6c58 100644
--- a/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java
+++ b/tests/src/com/android/tradefed/device/metric/GcovCodeCoverageCollectorTest.java
@@ -14,26 +14,28 @@
* limitations under the License.
*/
-package com.android.tradefed.testtype;
+package com.android.tradefed.device.metric;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.testtype.coverage.CoverageOptions;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
-import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.protobuf.ByteString;
@@ -45,7 +47,9 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.File;
@@ -64,9 +68,9 @@
import java.util.Map;
import java.util.zip.ZipFile;
-/** Unit tests for {@link NativeCodeCoverageListener}. */
+/** Unit tests for {@link GcovCodeCoverageCollector}. */
@RunWith(JUnit4.class)
-public class NativeCodeCoverageListenerTest {
+public class GcovCodeCoverageCollectorTest {
private static final String RUN_NAME = "SomeTest";
private static final int TEST_COUNT = 5;
@@ -74,6 +78,8 @@
@Rule public TemporaryFolder folder = new TemporaryFolder();
+ @Mock IConfiguration mMockConfiguration;
+ @Mock IInvocationContext mMockContext;
@Mock ITestDevice mMockDevice;
LogFileReader mFakeListener = new LogFileReader();
@@ -84,7 +90,7 @@
OptionSetter mCoverageOptionsSetter = null;
/** Object under test. */
- NativeCodeCoverageListener mCodeCoverageListener;
+ GcovCodeCoverageCollector mCodeCoverageListener;
@Before
public void setUp() throws ConfigurationException {
@@ -92,14 +98,19 @@
mCoverageOptions = new CoverageOptions();
mCoverageOptionsSetter = new OptionSetter(mCoverageOptions);
+
+ doReturn(mCoverageOptions).when(mMockConfiguration).getCoverageOptions();
+ doReturn(ImmutableList.of(mMockDevice)).when(mMockContext).getDevices();
+
+ mCodeCoverageListener = new GcovCodeCoverageCollector();
+ mCodeCoverageListener.setConfiguration(mMockConfiguration);
}
@Test
- public void test_logsCoverageZip() throws DeviceNotAvailableException, IOException {
- mCodeCoverageListener = new NativeCodeCoverageListener(mMockDevice, mFakeListener);
-
+ public void test_logsCoverageZip() throws Exception {
+ enableGcovCoverage();
// Setup mocks to write the coverage measurement to the file.
- doReturn(true).when(mMockDevice).enableAdbRoot();
+ doReturn(true).when(mMockDevice).isAdbRoot();
File tar =
createTar(
ImmutableMap.of(
@@ -110,6 +121,7 @@
doReturn(tar).when(mMockDevice).pullFile(anyString());
// Simulate a test run.
+ mCodeCoverageListener.init(mMockContext, mFakeListener);
mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
Map<String, String> metric = new HashMap<>();
mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
@@ -135,13 +147,13 @@
}
@Test
- public void testNoCoverageFiles_logsEmptyZip() throws DeviceNotAvailableException, IOException {
- mCodeCoverageListener = new NativeCodeCoverageListener(mMockDevice, mFakeListener);
-
- doReturn(true).when(mMockDevice).enableAdbRoot();
+ public void testNoCoverageFiles_logsEmptyZip() throws Exception {
+ enableGcovCoverage();
+ doReturn(true).when(mMockDevice).isAdbRoot();
doReturn(createTar(ImmutableMap.of())).when(mMockDevice).pullFile(anyString());
// Simulate a test run.
+ mCodeCoverageListener.init(mMockContext, mFakeListener);
mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
Map<String, String> metric = new HashMap<>();
mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
@@ -160,69 +172,60 @@
}
@Test
- public void testCoverageFlushAllProcesses_flushAllCommandCalled()
- throws ConfigurationException, DeviceNotAvailableException, IOException {
+ public void testCoverageFlushAllProcesses_flushAllCommandCalled() throws Exception {
+ enableGcovCoverage();
mCoverageOptionsSetter.setOptionValue("coverage-flush", "true");
- mCodeCoverageListener =
- new NativeCodeCoverageListener(mMockDevice, mCoverageOptions, mFakeListener);
-
- doReturn(true).when(mMockDevice).enableAdbRoot();
doReturn(true).when(mMockDevice).isAdbRoot();
doReturn(createTar(ImmutableMap.of())).when(mMockDevice).pullFile(anyString());
// Simulate a test run.
+ mCodeCoverageListener.init(mMockContext, mFakeListener);
mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
Map<String, String> metric = new HashMap<>();
mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
- // Verify the flush-all-coverage command was called.
- verify(mMockDevice).executeShellCommand("kill -37 -1");
+ // Verify the flush-all-coverage command was called twice - once on init(...) and once
+ // on test run end.
+ verify(mMockDevice, times(2)).executeShellCommand("kill -37 -1");
}
@Test
- public void testCoverageFlushSpecificProcesses_flushCommandCalled()
- throws ConfigurationException, DeviceNotAvailableException, IOException {
+ public void testCoverageFlushSpecificProcesses_flushCommandCalled() throws Exception {
+ enableGcovCoverage();
mCoverageOptionsSetter.setOptionValue("coverage-flush", "true");
mCoverageOptionsSetter.setOptionValue("coverage-processes", "mediaserver");
mCoverageOptionsSetter.setOptionValue("coverage-processes", "adbd");
- mCodeCoverageListener =
- new NativeCodeCoverageListener(mMockDevice, mCoverageOptions, mFakeListener);
-
- doReturn(true).when(mMockDevice).enableAdbRoot();
doReturn(true).when(mMockDevice).isAdbRoot();
doReturn("123").when(mMockDevice).getProcessPid("mediaserver");
doReturn("56789").when(mMockDevice).getProcessPid("adbd");
doReturn(createTar(ImmutableMap.of())).when(mMockDevice).pullFile(anyString());
// Simulate a test run.
+ mCodeCoverageListener.init(mMockContext, mFakeListener);
mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
Map<String, String> metric = new HashMap<>();
mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
- // Verify the flush-coverage command was called with the specific pids.
- verify(mMockDevice).executeShellCommand("kill -37 123 56789");
+ // Verify the flush-coverage command was called with the specific pids twice - once on
+ // init(...) and once on test run end.
+ verify(mMockDevice, times(2)).executeShellCommand("kill -37 123 56789");
}
@Test
- public void testFailure_unableToPullFile() throws DeviceNotAvailableException {
- mCodeCoverageListener = new NativeCodeCoverageListener(mMockDevice, mFakeListener);
-
+ public void testFailure_unableToPullFile() throws Exception {
+ enableGcovCoverage();
// Setup mocks.
- doReturn(true).when(mMockDevice).enableAdbRoot();
+ doReturn(true).when(mMockDevice).isAdbRoot();
// Simulate a test run.
+ mCodeCoverageListener.init(mMockContext, mFakeListener);
mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
Map<String, String> metric = new HashMap<>();
- try {
mCodeCoverageListener.testRunEnded(
ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
- fail("an exception should have been thrown.");
- } catch (VerifyException e) {
- // Expected
- }
// Verify testLog(..) was not called.
assertThat(mFakeListener.getLogs()).isEmpty();
@@ -230,10 +233,14 @@
@Test
public void testNoCollectOnTestEnd_noCoverageMeasurements() throws Exception {
- mCodeCoverageListener = new NativeCodeCoverageListener(mMockDevice, mFakeListener);
+ enableGcovCoverage();
mCodeCoverageListener.setCollectOnTestEnd(false);
- // Simute a test run.
+ // Setup mocks.
+ doReturn(true).when(mMockDevice).isAdbRoot();
+
+ // Simulate a test run.
+ mCodeCoverageListener.init(mMockContext, mFakeListener);
mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
Map<String, String> metric = new HashMap<>();
mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
@@ -268,6 +275,25 @@
}
}
+ @Test
+ public void testInit_adbRootAndCoverageFlushed() throws Exception {
+ enableGcovCoverage();
+
+ // Setup mocks.
+ when(mMockDevice.isAdbRoot()).thenReturn(false).thenReturn(true);
+ when(mMockDevice.enableAdbRoot()).thenReturn(true);
+
+ // Test init(...).
+ mCodeCoverageListener.init(mMockContext, mFakeListener);
+
+ InOrder inOrder = Mockito.inOrder(mMockDevice);
+ inOrder.verify(mMockDevice).isAdbRoot();
+ inOrder.verify(mMockDevice).enableAdbRoot();
+ inOrder.verify(mMockDevice).executeShellCommand("kill -37 -1");
+ inOrder.verify(mMockDevice).executeShellCommand(anyString());
+ inOrder.verify(mMockDevice).disableAdbRoot();
+ }
+
/** An {@link ITestInvocationListener} which reads test log data streams for verification. */
private static class LogFileReader implements ITestInvocationListener {
private List<ByteString> mLogs = new ArrayList<>();
@@ -303,4 +329,9 @@
}
return tarFile;
}
+
+ private void enableGcovCoverage() throws ConfigurationException {
+ mCoverageOptionsSetter.setOptionValue("coverage", "true");
+ mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "GCOV");
+ }
}
diff --git a/tests/src/com/android/tradefed/device/metric/GraphicsStatsMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/GraphicsStatsMetricCollectorTest.java
deleted file mode 100644
index cb051a6..0000000
--- a/tests/src/com/android/tradefed/device/metric/GraphicsStatsMetricCollectorTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link GraphicsStatsMetricCollector}. */
-//TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class GraphicsStatsMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice mDevice;
-
- @Spy GraphicsStatsMetricCollector mGfxInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mGfxInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), eq(LogDataType.GFX_INFO), any(InputStreamSource.class));
-
- doReturn(new File("graphics-1"))
- .when(mGfxInfoMetricCollector)
- .saveProcessOutput(any(ITestDevice.class), anyString(), anyString());
-
- doReturn(tempFolder.newFolder()).when(mGfxInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- mGfxInfoMetricCollector.collect(mDevice, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("graphics-1"), eq(LogDataType.GFX_INFO), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/IncidentReportCollectorTest.java b/tests/src/com/android/tradefed/device/metric/IncidentReportCollectorTest.java
index b5c142d..cce1aac 100644
--- a/tests/src/com/android/tradefed/device/metric/IncidentReportCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/IncidentReportCollectorTest.java
@@ -27,9 +27,11 @@
import android.os.IncidentProto;
import com.android.tradefed.config.ConfigurationDef;
+import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
@@ -37,6 +39,8 @@
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import org.junit.After;
@@ -49,6 +53,7 @@
import org.mockito.MockitoAnnotations;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
@@ -62,6 +67,7 @@
public class IncidentReportCollectorTest {
private static final TestDescription FAKE_TEST = new TestDescription("class", "test");
private static final String FAKE_REPORT_PATH = "/sdcard/incidents/final.pb";
+ private static final long FAKE_TIME = 10000;
// The {@code IncidentReportCollector} under test.
private IncidentReportCollector mIncidentCollector;
@@ -153,7 +159,7 @@
// Replay the test started and test ended events to simulate a test.
mIncidentCollector.testStarted(FAKE_TEST);
mIncidentCollector.testEnded(FAKE_TEST, TfMetricProtoUtil.upgradeConvert(deviceMetrics));
- // Ensure the file was processed and contains the expected report.
+ // Ensure the metrics/files were not pulled or processed.
verify(mInvocationListener, never()).testLog(any(), any(), any());
verify(mMockTestDevice, never()).pullFile(any(String.class));
}
@@ -176,4 +182,32 @@
// Ensure the processed file was not successfully processed or reported.
verify(mInvocationListener, never()).testLog(matches(".*processed.*"), any(), any());
}
+
+ /** Tests that a report is collected and reported if the test run end option is set. */
+ @Test
+ public void testCollectOnTestRunEnd() throws Exception {
+ OptionSetter setter = new OptionSetter(mIncidentCollector);
+ setter.setOptionValue("incident-on-test-run-end", "true");
+ // Replay the test started and test ended events to simulate a test.
+ mIncidentCollector.testStarted(FAKE_TEST);
+ mIncidentCollector.testEnded(
+ FAKE_TEST, TfMetricProtoUtil.upgradeConvert(new HashMap<String, String>()));
+ // Call test run end and ensure something is collected.
+ when(mMockTestDevice.executeShellV2Command(
+ eq(IncidentReportCollector.INCIDENT_REPORT_CMD),
+ any(FileOutputStream.class)))
+ .thenReturn(new CommandResult(CommandStatus.SUCCESS));
+ mIncidentCollector.testRunEnded(FAKE_TIME, new HashMap<String, Metric>());
+ // Ensure the expected "on-test-run-end" files were logged and processed.
+ verify(mInvocationListener)
+ .testLog(
+ matches(".*incident-on-test-run-end.*"),
+ eq(LogDataType.PB),
+ any(FileInputStreamSource.class));
+ verify(mInvocationListener)
+ .testLog(
+ matches(".*incident-on-test-run-end.*-processed"),
+ eq(LogDataType.PB),
+ any(ByteArrayInputStreamSource.class));
+ }
}
diff --git a/tests/src/com/android/tradefed/device/metric/IonHeapInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/IonHeapInfoMetricCollectorTest.java
deleted file mode 100644
index 2547672..0000000
--- a/tests/src/com/android/tradefed/device/metric/IonHeapInfoMetricCollectorTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link IonHeapInfoMetricCollector}. */
-// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class IonHeapInfoMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice mDevice;
-
- @Spy IonHeapInfoMetricCollector mIonHeapInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mIonHeapInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class));
-
- doReturn(new File("ion-system-2"))
- .when(mIonHeapInfoMetricCollector)
- .saveProcessOutput(
- any(ITestDevice.class), eq("cat /d/ion/heaps/system"), anyString());
- doReturn(new File("ion-audio-1"))
- .when(mIonHeapInfoMetricCollector)
- .saveProcessOutput(
- any(ITestDevice.class), eq("cat /d/ion/heaps/audio"), anyString());
-
- doReturn(tempFolder.newFolder()).when(mIonHeapInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- mIonHeapInfoMetricCollector.collect(mDevice, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("ion-system-2"), eq(LogDataType.TEXT), any());
- verify(mListener).testLog(eq("ion-audio-1"), eq(LogDataType.TEXT), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/testtype/JavaCodeCoverageListenerTest.java b/tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
similarity index 62%
rename from tests/src/com/android/tradefed/testtype/JavaCodeCoverageListenerTest.java
rename to tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
index b074de5..d4a42a3 100644
--- a/tests/src/com/android/tradefed/testtype/JavaCodeCoverageListenerTest.java
+++ b/tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.tradefed.testtype;
+package com.android.tradefed.device.metric;
-import static com.android.tradefed.testtype.JavaCodeCoverageListener.MERGE_COVERAGE_MEASUREMENTS_TEST_NAME;
+import static com.android.tradefed.device.metric.JavaCodeCoverageCollector.MERGE_COVERAGE_MEASUREMENTS_TEST_NAME;
import static com.google.common.truth.Truth.assertThat;
@@ -30,9 +30,12 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
@@ -41,7 +44,6 @@
import com.android.tradefed.util.JavaCodeCoverageFlusher;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
-import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.protobuf.ByteString;
@@ -74,9 +76,9 @@
import java.util.List;
import java.util.Map;
-/** Unit tests for {@link JavaCodeCoverageListener}. */
+/** Unit tests for {@link JavaCodeCoverageCollector}. */
@RunWith(JUnit4.class)
-public class JavaCodeCoverageListenerTest {
+public class JavaCodeCoverageCollectorTest {
private static final int PROBE_COUNT = 10;
@@ -90,13 +92,15 @@
@Rule public TemporaryFolder folder = new TemporaryFolder();
+ @Mock IConfiguration mMockConfiguration;
+ @Mock IInvocationContext mMockContext;
@Mock ITestDevice mMockDevice;
@Mock JavaCodeCoverageFlusher mMockFlusher;
@Spy LogFileReader mFakeListener = new LogFileReader();
/** Object under test. */
- JavaCodeCoverageListener mCodeCoverageListener;
+ JavaCodeCoverageCollector mCodeCoverageCollector;
CoverageOptions mCoverageOptions = null;
OptionSetter mCoverageOptionsSetter = null;
@@ -108,17 +112,38 @@
mCoverageOptions = new CoverageOptions();
mCoverageOptionsSetter = new OptionSetter(mCoverageOptions);
+ when(mMockConfiguration.getCoverageOptions()).thenReturn(mCoverageOptions);
+
+ when(mMockContext.getDevices()).thenReturn(ImmutableList.of(mMockDevice));
+
// Mock an unrooted device that has no issues enabling or disabling root.
when(mMockDevice.isAdbRoot()).thenReturn(false);
when(mMockDevice.enableAdbRoot()).thenReturn(true);
when(mMockDevice.disableAdbRoot()).thenReturn(true);
- mCodeCoverageListener =
- new JavaCodeCoverageListener(mMockDevice, mCoverageOptions, false, mFakeListener);
+ mCodeCoverageCollector = new JavaCodeCoverageCollector();
+ mCodeCoverageCollector.setConfiguration(mMockConfiguration);
+ }
+
+ @Test
+ public void testRunEnded_noCoverageEnabled_noop() throws Exception {
+ // Setup mocks.
+ HashMap<String, Metric> runMetrics = new HashMap<>();
+
+ // Simulate a test run.
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
+
+ // Verify testLog(..) was not called.
+ verify(mFakeListener, never())
+ .testLog(anyString(), eq(LogDataType.COVERAGE), eq(COVERAGE_MEASUREMENT));
}
@Test
public void testRunEnded_rootEnabled_logsCoverageMeasurement() throws Exception {
+ enableJavaCoverage();
+
// Setup mocks.
HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
mockCoverageFileOnDevice(DEVICE_PATH);
@@ -126,8 +151,9 @@
doReturn("").when(mMockDevice).executeShellCommand(anyString());
// Simulate a test run.
- mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
- mCodeCoverageListener.testRunEnded(ELAPSED_TIME, runMetrics);
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
// Verify testLog(..) was called with the coverage file.
verify(mFakeListener)
@@ -138,10 +164,17 @@
}
@Test
- public void testFailure_noCoverageMetric() {
+ public void testFailure_noCoverageMetric() throws Exception {
+ enableJavaCoverage();
+
+ // Setup mocks.
+ when(mMockDevice.executeShellCommand("ps -e")).thenReturn("");
+ when(mMockDevice.executeShellCommand("pm list packages -a")).thenReturn("");
+
// Simulate a test run.
- mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
- mCodeCoverageListener.testRunEnded(ELAPSED_TIME, new HashMap<String, Metric>());
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, new HashMap<String, Metric>());
// Verify that the test run is marked as a failure.
verify(mFakeListener).testRunFailed(anyString());
@@ -152,33 +185,33 @@
}
@Test
- public void testFailure_unableToPullFile() throws DeviceNotAvailableException {
+ public void testFailure_unableToPullFile() throws Exception {
+ enableJavaCoverage();
HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
doReturn("").when(mMockDevice).executeShellCommand(anyString());
doReturn(null).when(mMockDevice).pullFile(DEVICE_PATH);
// Simulate a test run.
- mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
- try {
- mCodeCoverageListener.testRunEnded(ELAPSED_TIME, runMetrics);
- fail("Exception not thrown");
- } catch (VerifyException expected) {
- }
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
- // Verify testLog(..) was not called.
verify(mFakeListener, never())
.testLog(anyString(), eq(LogDataType.COVERAGE), any(InputStreamSource.class));
}
@Test
public void testRunEnded_rootDisabled_enablesRootBeforePullingFiles() throws Exception {
+ enableJavaCoverage();
HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
mockCoverageFileOnDevice(DEVICE_PATH);
when(mMockDevice.isAdbRoot()).thenReturn(false);
doReturn("").when(mMockDevice).executeShellCommand(anyString());
- mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
- mCodeCoverageListener.testRunEnded(ELAPSED_TIME, runMetrics);
+ // Simulate a test run.
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
InOrder inOrder = inOrder(mMockDevice);
inOrder.verify(mMockDevice).enableAdbRoot();
@@ -187,29 +220,37 @@
}
@Test
- public void testRunEnded_rootDisabled_throwsIfCannotEnableRoot() throws Exception {
+ public void testRunEnded_rootDisabled_noLogIfCannotEnableRoot() throws Exception {
+ enableJavaCoverage();
HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
mockCoverageFileOnDevice(DEVICE_PATH);
when(mMockDevice.isAdbRoot()).thenReturn(false);
when(mMockDevice.enableAdbRoot()).thenReturn(false);
- mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
+ // Simulate a test run.
try {
- mCodeCoverageListener.testRunEnded(ELAPSED_TIME, runMetrics);
- fail("Exception not thrown");
- } catch (RuntimeException expected) {
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ fail("An exception should have been thrown.");
+ } catch (RuntimeException e) {
+ // Expected.
}
+
+ verify(mFakeListener, never())
+ .testLog(anyString(), eq(LogDataType.COVERAGE), any(InputStreamSource.class));
}
@Test
public void testRunEnded_rootDisabled_disablesRootAfterPullingFiles() throws Exception {
+ enableJavaCoverage();
HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
mockCoverageFileOnDevice(DEVICE_PATH);
when(mMockDevice.isAdbRoot()).thenReturn(false);
doReturn("").when(mMockDevice).executeShellCommand(anyString());
- mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
- mCodeCoverageListener.testRunEnded(ELAPSED_TIME, runMetrics);
+ // Simulate a test run.
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
InOrder inOrder = inOrder(mMockDevice);
inOrder.verify(mMockDevice).pullFile(anyString());
@@ -218,27 +259,13 @@
}
@Test
- public void testRunEnded_rootDisabled_throwsIfCannotDisableRoot() throws Exception {
- HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
- mockCoverageFileOnDevice(DEVICE_PATH);
- when(mMockDevice.isAdbRoot()).thenReturn(false);
- when(mMockDevice.disableAdbRoot()).thenReturn(false);
+ public void testMerge_producesSingleMeasurement() throws Exception {
+ enableJavaCoverage();
- mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
- try {
- mCodeCoverageListener.testRunEnded(ELAPSED_TIME, runMetrics);
- fail("Exception not thrown");
- } catch (RuntimeException expected) {
- }
- }
-
- @Test
- public void testMerge_producesSingleMeasurement()
- throws DeviceNotAvailableException, IOException {
// Setup mocks.
File coverageFile1 = folder.newFile("coverage1.ec");
try (OutputStream out = new FileOutputStream(coverageFile1)) {
- ByteString measurement = measurement(fullyCovered(JavaCodeCoverageListener.class));
+ ByteString measurement = measurement(fullyCovered(JavaCodeCoverageCollector.class));
measurement.writeTo(out);
}
@@ -246,13 +273,12 @@
try (OutputStream out = new FileOutputStream(coverageFile2)) {
ByteString measurement =
measurement(
- partiallyCovered(JavaCodeCoverageListener.class),
- partiallyCovered(JavaCodeCoverageListenerTest.class));
+ partiallyCovered(JavaCodeCoverageCollector.class),
+ partiallyCovered(JavaCodeCoverageCollectorTest.class));
measurement.writeTo(out);
}
- mCodeCoverageListener =
- new JavaCodeCoverageListener(mMockDevice, mCoverageOptions, true, mFakeListener);
+ mCodeCoverageCollector.setMergeMeasurements(true);
Map<String, String> metric = new HashMap<>();
metric.put("coverageFilePath", DEVICE_PATH);
@@ -261,12 +287,13 @@
doReturn("").when(mMockDevice).executeShellCommand(anyString());
doReturn(coverageFile1).doReturn(coverageFile2).when(mMockDevice).pullFile(DEVICE_PATH);
- mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
- mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
- mCodeCoverageListener.testRunStarted(RUN_NAME + "2", TEST_COUNT);
- mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
- mCodeCoverageListener.testRunStarted(MERGE_COVERAGE_MEASUREMENTS_TEST_NAME, TEST_COUNT);
- mCodeCoverageListener.testRunEnded(ELAPSED_TIME, new HashMap<String, Metric>());
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
+ mCodeCoverageCollector.testRunStarted(RUN_NAME + "2", TEST_COUNT);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
+ mCodeCoverageCollector.testRunStarted(MERGE_COVERAGE_MEASUREMENTS_TEST_NAME, TEST_COUNT);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, new HashMap<String, Metric>());
// Capture the merged coverage measurements that were passed to the fake listener.
ArgumentCaptor<ByteString> stream = ArgumentCaptor.forClass(ByteString.class);
@@ -280,18 +307,20 @@
boolean[] fullyCovered = new boolean[PROBE_COUNT];
Arrays.fill(fullyCovered, Boolean.TRUE);
- assertThat(execData.contains(vmName(JavaCodeCoverageListener.class))).isTrue();
- assertThat(getProbes(JavaCodeCoverageListener.class, execData)).isEqualTo(fullyCovered);
+ assertThat(execData.contains(vmName(JavaCodeCoverageCollector.class))).isTrue();
+ assertThat(getProbes(JavaCodeCoverageCollector.class, execData)).isEqualTo(fullyCovered);
boolean[] partiallyCovered = new boolean[PROBE_COUNT];
partiallyCovered[0] = true;
- assertThat(execData.contains(vmName(JavaCodeCoverageListenerTest.class))).isTrue();
- assertThat(getProbes(JavaCodeCoverageListenerTest.class, execData))
+ assertThat(execData.contains(vmName(JavaCodeCoverageCollectorTest.class))).isTrue();
+ assertThat(getProbes(JavaCodeCoverageCollectorTest.class, execData))
.isEqualTo(partiallyCovered);
}
@Test
public void testCoverageFlush_producesMultipleMeasurements() throws Exception {
+ enableJavaCoverage();
+
List<String> coverageFileList =
ImmutableList.of(
"/data/misc/trace/com.android.test1.ec",
@@ -312,13 +341,57 @@
.when(mMockDevice)
.executeShellCommand("find /data/misc/trace -name '*.ec'");
- mCodeCoverageListener.setCoverageFlusher(mMockFlusher);
+ mCodeCoverageCollector.setCoverageFlusher(mMockFlusher);
// Simulate a test run.
- mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
Map<String, String> metric = new HashMap<>();
metric.put("coverageFilePath", DEVICE_PATH);
- mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
+ }
+
+ @Test
+ public void testRunningProcess_coverageFileNotDeleted() throws Exception {
+ enableJavaCoverage();
+
+ List<String> coverageFileList =
+ ImmutableList.of(
+ "/data/misc/trace/coverage1.ec",
+ "/data/misc/trace/coverage2.ec",
+ "/data/misc/trace/jacoco-123.mm.ec",
+ "/data/misc/trace/jacoco-456.mm.ec");
+ String psOutput =
+ "USER PID PPID VSZ RSS WCHAN PC S NAME\n"
+ + "bluetooth 123 1366 123 456 SyS_epoll+ 0 S com.android.bluetooth\n"
+ + "radio 890 1 7890 123 binder_io+ 0 S com.android.phone\n"
+ + "root 11 1234 567 890 binder_io+ 0 S not.a.java.package\n";
+
+ // Setup mocks.
+ mockCoverageFileOnDevice(DEVICE_PATH);
+
+ for (String additionalFile : coverageFileList) {
+ mockCoverageFileOnDevice(additionalFile);
+ }
+
+ doReturn("").when(mMockDevice).executeShellCommand("pm list packages -a");
+ doReturn(psOutput).when(mMockDevice).executeShellCommand("ps -e");
+ doReturn(String.join("\n", coverageFileList))
+ .when(mMockDevice)
+ .executeShellCommand("find /data/misc/trace -name '*.ec'");
+
+ // Simulate a test run.
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
+ Map<String, String> metric = new HashMap<>();
+ metric.put("coverageFilePath", DEVICE_PATH);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
+
+ // Verify the correct files were deleted and some files were not deleted.
+ verify(mMockDevice).deleteFile(coverageFileList.get(0));
+ verify(mMockDevice).deleteFile(coverageFileList.get(1));
+ verify(mMockDevice, never()).deleteFile(coverageFileList.get(2));
+ verify(mMockDevice).deleteFile(coverageFileList.get(3));
}
private void mockCoverageFileOnDevice(String devicePath)
@@ -369,13 +442,18 @@
private static <T> boolean[] getProbes(Class<T> clazz, ExecutionDataStore execData)
throws IOException {
- return execData.get(classId(clazz), vmName(clazz), PROBE_COUNT).getProbes();
+ return execData.get(classId(clazz), vmName(clazz), PROBE_COUNT).getProbesCopy();
}
private static HashMap<String, Metric> createMetricsWithCoverageMeasurement(String devicePath) {
return TfMetricProtoUtil.upgradeConvert(ImmutableMap.of("coverageFilePath", devicePath));
}
+ private void enableJavaCoverage() throws ConfigurationException {
+ mCoverageOptionsSetter.setOptionValue("coverage", "true");
+ mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "JACOCO");
+ }
+
/** An {@link ITestInvocationListener} which reads test log data streams for verification. */
private static class LogFileReader implements ITestInvocationListener {
/**
diff --git a/tests/src/com/android/tradefed/device/metric/MemInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/MemInfoMetricCollectorTest.java
deleted file mode 100644
index c56a081..0000000
--- a/tests/src/com/android/tradefed/device/metric/MemInfoMetricCollectorTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link MemInfoMetricCollector}. */
-// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class MemInfoMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice mDevice;
-
- @Spy MemInfoMetricCollector mMemInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mMemInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(
- anyString(), eq(LogDataType.COMPACT_MEMINFO), any(InputStreamSource.class));
-
- doReturn(new File("compact-meminfo-1"))
- .when(mMemInfoMetricCollector)
- .saveProcessOutput(any(ITestDevice.class), anyString(), anyString());
-
- doReturn(tempFolder.newFolder()).when(mMemInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
- when(mMemInfoMetricCollector.getFileSuffix()).thenReturn("1");
-
- mMemInfoMetricCollector.collect(mDevice, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("compact-meminfo-1"), eq(LogDataType.COMPACT_MEMINFO), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/PagetypeInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/PagetypeInfoMetricCollectorTest.java
deleted file mode 100644
index 9348743..0000000
--- a/tests/src/com/android/tradefed/device/metric/PagetypeInfoMetricCollectorTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link PagetypeInfoMetricCollector}. */
-// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class PagetypeInfoMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice mDevice;
-
- @Spy PagetypeInfoMetricCollector mPagetypeInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mPagetypeInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class));
-
- doReturn(new File("pagetypeinfo-1"))
- .when(mPagetypeInfoMetricCollector)
- .saveProcessOutput(any(ITestDevice.class), anyString(), anyString());
-
- doReturn(tempFolder.newFolder()).when(mPagetypeInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
- when(mPagetypeInfoMetricCollector.getFileSuffix()).thenReturn("1");
-
- mPagetypeInfoMetricCollector.collect(mDevice, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("pagetypeinfo-1"), eq(LogDataType.TEXT), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
index 24418a6..9d131c5 100644
--- a/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
@@ -16,6 +16,7 @@
package com.android.tradefed.device.metric;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
@@ -120,6 +121,47 @@
assertTrue("Trace duration metrics not available but expected.",
currentMetrics.get("perfetto_trace_extractor_runtime").getMeasurements()
.getSingleDouble() >= 0);
+ assertNull("Trace duration metrics not available but expected.",
+ currentMetrics.get("perfetto_trace_file_size_bytes"));
+ }
+
+ @Test
+ public void testProcessingFlowWithFileSizeMetric() throws Exception {
+
+ OptionSetter setter = new OptionSetter(mPerfettoMetricCollector);
+ setter.setOptionValue("pull-pattern-keys", "perfettofile");
+ setter.setOptionValue("perfetto-binary-path", "trx");
+ setter.setOptionValue("convert-metric-file", "false");
+ setter.setOptionValue("collect-perfetto-file-size", "true");
+ HashMap<String, Metric> currentMetrics = new HashMap<>();
+ currentMetrics.put("perfettofile", TfMetricProtoUtil.stringToMetric("/data/trace.pb"));
+ Mockito.when(mMockDevice.pullFile(Mockito.eq("/data/trace.pb")))
+ .thenReturn(new File("trace"));
+
+ TestDescription testDesc = new TestDescription("xyz", "abc");
+ CommandResult cr = new CommandResult();
+ cr.setStatus(CommandStatus.SUCCESS);
+ cr.setStdout("abc:efg");
+
+ Mockito.doReturn(cr).when(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
+ Mockito.any(), Mockito.any(), Mockito.any());
+
+ mPerfettoMetricCollector.testStarted(testDesc);
+ mPerfettoMetricCollector.testEnded(testDesc, currentMetrics);
+
+ Mockito.verify(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
+ Mockito.any(), Mockito.any(), Mockito.any());
+ Mockito.verify(mMockListener)
+ .testLog(Mockito.eq("trace"), Mockito.eq(LogDataType.PERFETTO), Mockito.any());
+ assertTrue("Expected two metrics that includes success status",
+ currentMetrics.get("perfetto_trace_extractor_status").getMeasurements()
+ .getSingleString().equals("1"));
+ assertTrue("Trace duration metrics not available but expected.",
+ currentMetrics.get("perfetto_trace_extractor_runtime").getMeasurements()
+ .getSingleDouble() >= 0);
+ assertTrue("Trace file size metric is not available in the final metrics.",
+ currentMetrics.get("perfetto_trace_file_size_bytes").getMeasurements()
+ .getSingleDouble() >= 0);
}
@Test
@@ -328,3 +370,4 @@
}
}
+
diff --git a/tests/src/com/android/tradefed/device/metric/ProcessMaxMemoryCollectorTest.java b/tests/src/com/android/tradefed/device/metric/ProcessMaxMemoryCollectorTest.java
deleted file mode 100644
index b66138f..0000000
--- a/tests/src/com/android/tradefed/device/metric/ProcessMaxMemoryCollectorTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ITestInvocationListener;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-
-import java.util.Collections;
-import java.util.HashMap;
-
-/** Unit tests for {@link ProcessMaxMemoryCollector}. */
-@RunWith(JUnit4.class)
-public class ProcessMaxMemoryCollectorTest {
-
- private ProcessMaxMemoryCollector mCollector;
- private IInvocationContext mContext;
- private ITestDevice mDevice;
- private ITestInvocationListener mListener;
-
- private static final String TEST_INPUT =
- "time,28506638,177086152\n"
- + "4,938,system_server,11,22,N/A,44,0,0,N/A,0,0,0,N/A,0,27613,14013,176602,"
- + "218228,0,0,122860,122860,1512,1412,5740,8664,0,0,154924,154924,27568,"
- + "13972,11916,53456,0,0,123008,123008,0,0,0,0,0,0,0,0,Dalvik Other,3662,0,"
- + "104,0,3660,0,0,0,Stack,1576,0,8,0,1576,0,0,0,Cursor,0,0,0,0,0,0,0,0,"
- + "Ashmem,156,0,20,0,148,0,0,0,Gfx dev,100,0,48,0,76,0,0,0,Other dev,116,0,"
- + "164,0,0,96,0,0,.so mmap,7500,2680,3984,21864,904,2680,0,0,.jar mmap,0,0,0,"
- + "0,0,0,0,0,.apk mmap,72398,71448,0,11736,0,71448,0,0,.ttf mmap,0,0,0,0,0,0,"
- + "0,0,.dex mmap,76874,46000,0,83644,40,46000,0,0,.oat mmap,8127,2684,64,"
- + "26652,0,2684,0,0,.art mmap,1991,48,972,10004,1544,48,0,0,Other mmap,137,0,"
- + "44,1024,4,52,0,0,EGL mtrack,0,0,0,0,0,0,0,0,GL mtrack,111,222,333,444,555,"
- + "666,777,888,";
-
- @Before
- public void setup() throws Exception {
- mCollector = new ProcessMaxMemoryCollector();
- mContext = mock(IInvocationContext.class);
- mDevice = mock(ITestDevice.class);
- when(mContext.getDevices()).thenReturn(Collections.singletonList(mDevice));
- mListener = mock(ITestInvocationListener.class);
- mCollector.init(mContext, mListener);
- OptionSetter setter = new OptionSetter(mCollector);
- setter.setOptionValue("memory-usage-process-name", "system_server");
- }
-
- @Test
- public void testCollector() throws Exception {
- when(mDevice.executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server")))
- .thenReturn(TEST_INPUT);
-
- DeviceMetricData data = new DeviceMetricData(mContext);
- mCollector.onStart(data);
- mCollector.collect(mDevice, data);
- mCollector.onEnd(data);
-
- verify(mDevice).executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server"));
-
- HashMap<String, Metric> results = new HashMap<>();
- data.addToMetrics(results);
- assertEquals(218228, results.get("MAX_PSS#system_server").getMeasurements().getSingleInt());
- assertEquals(53456, results.get("MAX_USS#system_server").getMeasurements().getSingleInt());
- }
-
- @Test
- public void testCollectorNoProcess() throws Exception {
- when(mDevice.executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server")))
- .thenReturn("No process found for: system_server");
-
- DeviceMetricData data = new DeviceMetricData(mContext);
- mCollector.onStart(data);
- mCollector.collect(mDevice, data);
- mCollector.onEnd(data);
-
- verify(mDevice).executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server"));
-
- HashMap<String, Metric> results = new HashMap<>();
- data.addToMetrics(results);
- assertTrue(results.isEmpty());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollectorTest.java
deleted file mode 100644
index 13605f3..0000000
--- a/tests/src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollectorTest.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * 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.device.metric;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.util.RunUtil;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/** Unit tests for {@link ScheduleMultipleDeviceMetricCollector}. */
-@RunWith(JUnit4.class)
-public class ScheduleMultipleDeviceMetricCollectorTest {
- @Rule public final TemporaryFolder folder = new TemporaryFolder();
- @Mock private ITestDevice mTestDevice;
- @Mock private ITestInvocationListener mMockListener;
- @Spy private ScheduleMultipleDeviceMetricCollector mMultipleMetricCollector;
-
- private IInvocationContext mContext;
-
- static class TestMeminfoCollector extends ScheduledDeviceMetricCollector {
- private int mInternalCounter = 0;
- private String key = "meminfo";
-
- TestMeminfoCollector() {
- setTag("meminfoInterval");
- }
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData)
- throws InterruptedException {
- mInternalCounter++;
- runData.addMetricForDevice(
- device,
- key + mInternalCounter,
- Metric.newBuilder()
- .setMeasurements(
- Measurements.newBuilder()
- .setSingleString("value" + mInternalCounter)));
- }
- }
-
- static class TestJankinfoCollector extends ScheduledDeviceMetricCollector {
- private int mInternalCounter = 0;
- private String key = "jankinfo";
-
- TestJankinfoCollector() {
- setTag("jankInterval");
- }
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData)
- throws InterruptedException {
- mInternalCounter++;
- runData.addMetricForDevice(
- device,
- key + mInternalCounter,
- Metric.newBuilder()
- .setMeasurements(
- Measurements.newBuilder()
- .setSingleString("value" + mInternalCounter)));
- }
- }
-
- static class TestFragmentationCollector extends ScheduledDeviceMetricCollector {
- private int mInternalCounter = 0;
- private String key = "fragmentation";
-
- TestFragmentationCollector() {
- setTag("fragmentationInterval");
- }
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData)
- throws InterruptedException {
- mInternalCounter++;
- runData.addMetricForDevice(
- device,
- key + mInternalCounter,
- Metric.newBuilder()
- .setMeasurements(
- Measurements.newBuilder()
- .setSingleString("value" + mInternalCounter)));
- }
- }
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mContext = new InvocationContext();
- mContext.addAllocatedDevice("test device", mTestDevice);
- }
-
- @Test
- public void testMultipleMetricCollector_success() throws Exception {
- OptionSetter setter = new OptionSetter(mMultipleMetricCollector);
-
- // Set up the metric collection storage path.
- File metricStoragePath = folder.newFolder();
- setter.setOptionValue("metric-storage-path", metricStoragePath.toString());
-
- // Set up the intervals.
- Map<String, Long> intervals = new HashMap<>();
- intervals.put("meminfoInterval", 100L);
- intervals.put("fragmentationInterval", 100L);
- intervals.put("jankInterval", 100L);
- for (String key : intervals.keySet()) {
- setter.setOptionValue(
- "metric-collection-intervals", key, intervals.get(key).toString());
- }
-
- // Request the collectors.
- List<String> classnames = new ArrayList<>();
- classnames.add(TestMeminfoCollector.class.getName());
- classnames.add(TestJankinfoCollector.class.getName());
- classnames.add(TestFragmentationCollector.class.getName());
- for (String key : classnames) {
- setter.setOptionValue("metric-collector-command-classes", key);
- }
-
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- // Start the tests.
- HashMap<String, Metric> metrics = new HashMap<>();
- mMultipleMetricCollector.init(mContext, mMockListener);
- try {
- mMultipleMetricCollector.onTestRunStart(runData);
- RunUtil.getDefault().sleep(500);
- } finally {
- mMultipleMetricCollector.onTestRunEnd(runData, metrics);
- }
-
- // We give it 500msec to run and 100msec interval we should easily have at least run all the
- // metrics once.
- // assert that the metrics contains filenames of all the collected metrics.
- HashMap<String, Metric> metricsCollected = new HashMap<>();
- runData.addToMetrics(metricsCollected);
-
- assertTrue(metricsCollected.containsKey("jankinfo1"));
- assertTrue(metricsCollected.containsKey("meminfo1"));
- assertTrue(metricsCollected.containsKey("fragmentation1"));
- }
-
- @Test
- public void testMultipleMetricCollector_noFailureEvenIfNoCollectorRequested() throws Exception {
- HashMap<String, Metric> metrics = new HashMap<>();
- mMultipleMetricCollector.init(mContext, mMockListener);
-
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- try {
- mMultipleMetricCollector.onTestRunStart(runData);
- RunUtil.getDefault().sleep(500);
- } finally {
- mMultipleMetricCollector.onTestRunEnd(runData, metrics);
- }
-
- // No metrics should have been collected.
- HashMap<String, Metric> metricsCollected = new HashMap<>();
- runData.addToMetrics(metricsCollected);
-
- assertEquals(0, metricsCollected.size());
- }
-
- /** Test that if a specified collector does not exists, we ignore it and proceed. */
- @Test
- public void testMultipleMetricCollector_collectorNotFound() throws Exception {
- OptionSetter setter = new OptionSetter(mMultipleMetricCollector);
-
- // Set up the metric collection storage path.
- File metricStoragePath = folder.newFolder();
- setter.setOptionValue("metric-storage-path", metricStoragePath.toString());
-
- // Set up the intervals.
- Map<String, Long> intervals = new HashMap<>();
- intervals.put("meminfoInterval", 100L);
- for (String key : intervals.keySet()) {
- setter.setOptionValue(
- "metric-collection-intervals", key, intervals.get(key).toString());
- }
-
- // Request the collectors.
- List<String> classnames = new ArrayList<>();
- classnames.add(TestMeminfoCollector.class.getName());
- classnames.add("this.does.not.exists.collector");
- for (String key : classnames) {
- setter.setOptionValue("metric-collector-command-classes", key);
- }
-
- HashMap<String, Metric> metrics = new HashMap<>();
- mMultipleMetricCollector.init(mContext, mMockListener);
-
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- try {
- mMultipleMetricCollector.onTestRunStart(runData);
- RunUtil.getDefault().sleep(500);
- } finally {
- mMultipleMetricCollector.onTestRunEnd(runData, metrics);
- }
-
- // No metrics should have been collected.
- HashMap<String, Metric> metricsCollected = new HashMap<>();
- runData.addToMetrics(metricsCollected);
-
- assertTrue(metricsCollected.containsKey("meminfo1"));
- }
-
- @Test
- public void testMultipleMetricCollector_failsForNonNegativeInterval() throws Exception {
- String expectedStderr =
- "class com.android.tradefed.device.metric."
- + "ScheduleMultipleDeviceMetricCollectorTest$TestJankinfoCollector expects "
- + "a non negative interval.";
-
- OptionSetter setter = new OptionSetter(mMultipleMetricCollector);
-
- // Set up the metric collection storage path.
- setter.setOptionValue("metric-storage-path", folder.newFolder().toString());
-
- // Set up the interval.
- Map<String, Long> intervals = new HashMap<>();
- intervals.put("jankInterval", -100L);
- for (String key : intervals.keySet()) {
- setter.setOptionValue(
- "metric-collection-intervals", key, intervals.get(key).toString());
- }
-
- // Set up the classname.
- List<String> classnames = new ArrayList<>();
- classnames.add(TestJankinfoCollector.class.getName());
- for (String key : classnames) {
- setter.setOptionValue("metric-collector-command-classes", key);
- }
-
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- // Start the tests, which should fail with the expected error message.
- mMultipleMetricCollector.init(mContext, mMockListener);
-
- try {
- mMultipleMetricCollector.onTestRunStart(runData);
- fail("Should throw illegal argument exception in case of negative intervals.");
- } catch (IllegalArgumentException e) {
- assertEquals(expectedStderr, e.getMessage());
- }
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java
deleted file mode 100644
index 1f23a43..0000000
--- a/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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.device.metric;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.util.RunUtil;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/** Unit tests for {@link ScheduledDeviceMetricCollector}. */
-@RunWith(JUnit4.class)
-public class ScheduledDeviceMetricCollectorTest {
- private Map<String, ITestDevice> mDevicesWithNames = new HashMap<>();
-
- public static class TestableAsyncTimer extends ScheduledDeviceMetricCollector {
- private int mInternalCounter = 0;
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- mInternalCounter++;
- runData.addMetricForDevice(
- device,
- "key" + mInternalCounter,
- Metric.newBuilder()
- .setMeasurements(
- Measurements.newBuilder()
- .setSingleString("value" + mInternalCounter)));
- }
- }
-
- private TestableAsyncTimer mBase;
- private IInvocationContext mContext;
- private ITestInvocationListener mMockListener;
-
- @Before
- public void setUp() {
- mBase = new TestableAsyncTimer();
- mContext = new InvocationContext();
- mMockListener = Mockito.mock(ITestInvocationListener.class);
- }
-
- /** Test the periodic run of the collector once testRunStarted has been called. */
- @Test
- public void testSetupAndPeriodicRunSingleDevice() throws Exception {
- // Setup the context with the devices.
- mDevicesWithNames.put("test device 1", mock(ITestDevice.class));
- mContext.addAllocatedDevice(mDevicesWithNames);
-
- OptionSetter setter = new OptionSetter(mBase);
- // 100 ms interval
- setter.setOptionValue("interval", "100");
- HashMap<String, Metric> metrics = new HashMap<>();
- mBase.init(mContext, mMockListener);
- try {
- mBase.testRunStarted("testRun", 1);
- RunUtil.getDefault().sleep(500);
- } finally {
- mBase.testRunEnded(0l, metrics);
- }
- // We give it 500msec to run and 100msec interval we should easily have at least three
- // iterations
- assertTrue(metrics.containsKey("key1"));
- assertTrue(metrics.containsKey("key2"));
- assertTrue(metrics.containsKey("key3"));
- }
-
- /**
- * Test the periodic run of the collector on multiple devices once testRunStarted has been
- * called.
- */
- @Test
- public void testSetupAndPeriodicRunMultipleDevices() throws Exception {
- // Setup the context with the devices.
- mDevicesWithNames.put("test device 1", mock(ITestDevice.class));
- mDevicesWithNames.put("test device 2", mock(ITestDevice.class));
- mContext.addAllocatedDevice(mDevicesWithNames);
-
- OptionSetter setter = new OptionSetter(mBase);
- // 100 ms interval
- setter.setOptionValue("interval", "100");
- HashMap<String, Metric> metrics = new HashMap<>();
- mBase.init(mContext, mMockListener);
- try {
- mBase.testRunStarted("testRun", 1);
- RunUtil.getDefault().sleep(500);
- } finally {
- mBase.testRunEnded(0l, metrics);
- }
- // We give it 500msec to run and 100msec interval we should easily have at least two
- // iterations one for each device. The order of execution is arbitrary so check for prefix
- // only.
- assertTrue(metrics.keySet().stream().anyMatch(key -> key.startsWith("{test device 1}")));
- assertTrue(metrics.keySet().stream().anyMatch(key -> key.startsWith("{test device 2}")));
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/TemperatureCollectorTest.java b/tests/src/com/android/tradefed/device/metric/TemperatureCollectorTest.java
deleted file mode 100644
index a4c8550..0000000
--- a/tests/src/com/android/tradefed/device/metric/TemperatureCollectorTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ITestInvocationListener;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Collections;
-import java.util.HashMap;
-
-/** Unit tests for {@link TemperatureCollector}. */
-@RunWith(JUnit4.class)
-public class TemperatureCollectorTest {
-
- private TemperatureCollector mCollector;
- private IInvocationContext mContext;
- private ITestDevice mDevice;
- private ITestInvocationListener mListener;
-
- @Before
- public void setup() throws Exception {
- mCollector = new TemperatureCollector();
- mContext = mock(IInvocationContext.class);
- mDevice = mock(ITestDevice.class);
- when(mDevice.isAdbRoot()).thenReturn(true);
- when(mContext.getDevices()).thenReturn(Collections.singletonList(mDevice));
- mListener = mock(ITestInvocationListener.class);
- mCollector.init(mContext, mListener);
- OptionSetter setter = new OptionSetter(mCollector);
- setter.setOptionValue(
- "device-temperature-file-path", "/sys/class/hwmon/hwmon1/device/msm_therm");
- }
-
- @Test
- public void testCollector() throws Exception {
- when(mDevice.executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm")))
- .thenReturn("Result:32 Raw:7e51", "Result:22 Raw:7b51");
-
- DeviceMetricData data = new DeviceMetricData(mContext);
- mCollector.onStart(data);
- mCollector.collect(mDevice, data);
- mCollector.collect(mDevice, data);
- mCollector.onEnd(data);
-
- verify(mDevice, times(2))
- .executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm"));
-
- HashMap<String, Metric> results = new HashMap<>();
- data.addToMetrics(results);
- assertEquals(32D, results.get("max_temperature").getMeasurements().getSingleDouble(), 0);
- assertEquals(22D, results.get("min_temperature").getMeasurements().getSingleDouble(), 0);
- }
-
- @Test
- public void testCollectorNoData() throws Exception {
- when(mDevice.executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm")))
- .thenReturn(
- "cat: /sys/class/hwmon/hwmon1/device/msm_therm: No such file or directory");
-
- DeviceMetricData data = new DeviceMetricData(mContext);
- mCollector.onStart(data);
- mCollector.collect(mDevice, data);
- mCollector.onEnd(data);
-
- verify(mDevice).executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm"));
-
- HashMap<String, Metric> results = new HashMap<>();
- data.addToMetrics(results);
- assertTrue(results.isEmpty());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/TraceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/TraceMetricCollectorTest.java
deleted file mode 100644
index ffb3727..0000000
--- a/tests/src/com/android/tradefed/device/metric/TraceMetricCollectorTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.metric;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link TraceMetricCollector}. */
-// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class TraceMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice mDevice;
-
- @Spy TraceMetricCollector mTraceInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mTraceInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class));
-
- doReturn(new File("trace-1"))
- .when(mTraceInfoMetricCollector)
- .saveProcessOutput(any(ITestDevice.class), anyString(), anyString());
-
- doReturn(tempFolder.newFolder()).when(mTraceInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
- when(mTraceInfoMetricCollector.getFileSuffix()).thenReturn("1");
-
- mTraceInfoMetricCollector.collect(mDevice, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("trace-1"), eq(LogDataType.TEXT), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java b/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
index dfb8b95..65e7c06 100644
--- a/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
+++ b/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
@@ -16,6 +16,8 @@
package com.android.tradefed.invoker;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -425,4 +427,26 @@
FileUtil.deleteFile(buildFile);
}
}
+
+ @Test
+ public void testBuildInfo_testTag() throws Exception {
+ IBuildInfo info = new BuildInfo();
+ assertEquals("stub", info.getTestTag());
+ File testsDir = FileUtil.createTempDir("doesnt_matter_testsdir");
+ try {
+ info.setFile(BuildInfoFileKey.TESTDIR_IMAGE, testsDir, "tests");
+ mContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, info);
+ mConfig.getCommandOptions().setTestTag("test");
+ TestInformation testInfo =
+ TestInformation.newBuilder().setInvocationContext(mContext).build();
+ assertNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY));
+ mExecution.fetchBuild(testInfo, mConfig, null, null);
+ // Build test tag was updated
+ assertEquals("test", info.getTestTag());
+ // Execution file was back filled
+ assertNotNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY));
+ } finally {
+ FileUtil.recursiveDelete(testsDir);
+ }
+ }
}
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
index b66ba56..768ae2c 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
@@ -174,15 +174,17 @@
EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger);
EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc);
mMockLogger.init();
+ EasyMock.expectLastCall().times(2);
EasyMock.expect(mMockLogger.getLog())
.andReturn(new ByteArrayInputStreamSource("fake".getBytes()));
mMockLogger.closeLog();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
mMockLogRegistry.registerLogger(mMockLogger);
+ EasyMock.expectLastCall().times(2);
mMockLogRegistry.dumpToGlobalLog(mMockLogger);
mMockLogRegistry.unregisterLogger();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty");
EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions());
@@ -270,15 +272,17 @@
EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger);
EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc);
mMockLogger.init();
+ EasyMock.expectLastCall().times(2);
EasyMock.expect(mMockLogger.getLog())
.andReturn(new ByteArrayInputStreamSource("fake".getBytes()));
mMockLogger.closeLog();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
mMockLogRegistry.registerLogger(mMockLogger);
+ EasyMock.expectLastCall().times(2);
mMockLogRegistry.dumpToGlobalLog(mMockLogger);
mMockLogRegistry.unregisterLogger();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty");
EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions());
@@ -352,15 +356,17 @@
EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger);
EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc);
mMockLogger.init();
+ EasyMock.expectLastCall().times(2);
EasyMock.expect(mMockLogger.getLog())
.andReturn(new ByteArrayInputStreamSource("fake".getBytes()));
mMockLogger.closeLog();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
mMockLogRegistry.registerLogger(mMockLogger);
+ EasyMock.expectLastCall().times(2);
mMockLogRegistry.dumpToGlobalLog(mMockLogger);
mMockLogRegistry.unregisterLogger();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty");
EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions());
@@ -442,15 +448,17 @@
EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger);
EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc);
mMockLogger.init();
+ EasyMock.expectLastCall().times(2);
EasyMock.expect(mMockLogger.getLog())
.andReturn(new ByteArrayInputStreamSource("fake".getBytes()));
mMockLogger.closeLog();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
mMockLogRegistry.registerLogger(mMockLogger);
+ EasyMock.expectLastCall().times(2);
mMockLogRegistry.dumpToGlobalLog(mMockLogger);
mMockLogRegistry.unregisterLogger();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty");
EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions());
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
index f8c4eb2..dc53f52 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
@@ -412,11 +412,6 @@
setupMockFailureListeners(exception);
setupInvoke();
- EasyMock.reset(mMockLogger, mMockLogRegistry);
- mMockLogRegistry.registerLogger(mMockLogger);
- mMockLogger.init();
- mMockLogger.closeLog();
- mMockLogRegistry.unregisterLogger();
IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
CommandOptions cmdOptions = new CommandOptions();
final String expectedTestTag = "TEST_TAG";
@@ -449,13 +444,6 @@
setupMockFailureListenersAny(
new BuildRetrievalError("fake", InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR),
true);
-
- EasyMock.reset(mMockLogger, mMockLogRegistry);
- mMockLogRegistry.registerLogger(mMockLogger);
- mMockLogger.init();
- mMockLogger.closeLog();
- mMockLogRegistry.unregisterLogger();
-
EasyMock.expect(mMockLogger.getLog()).andReturn(EMPTY_STREAM_SOURCE);
EasyMock.expect(mMockDevice.getLogcat()).andReturn(EMPTY_STREAM_SOURCE).times(2);
mMockDevice.clearLogcat();
@@ -484,12 +472,6 @@
"No build found to test.", InfraErrorIdentifier.ARTIFACT_NOT_FOUND),
true);
- EasyMock.reset(mMockLogger, mMockLogRegistry);
- mMockLogRegistry.registerLogger(mMockLogger);
- mMockLogger.init();
- mMockLogger.closeLog();
- mMockLogRegistry.unregisterLogger();
-
EasyMock.expect(mMockLogger.getLog()).andReturn(EMPTY_STREAM_SOURCE);
EasyMock.expect(mMockDevice.getLogcat()).andReturn(EMPTY_STREAM_SOURCE).times(2);
mMockDevice.clearLogcat();
@@ -520,13 +502,6 @@
"No build found to test.", InfraErrorIdentifier.ARTIFACT_NOT_FOUND),
true, /* don't expect host log */
false);
-
- EasyMock.reset(mMockLogger, mMockLogRegistry);
- mMockLogRegistry.registerLogger(mMockLogger);
- mMockLogger.init();
- mMockLogger.closeLog();
- EasyMock.expectLastCall().times(2);
-
IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
mStubConfiguration.setTest(test);
// Host log fails to report
@@ -537,7 +512,7 @@
Capture<IBuildInfo> captured = new Capture<>();
mMockBuildProvider.cleanUp(EasyMock.capture(captured));
mMockLogRegistry.unregisterLogger();
- EasyMock.expectLastCall().times(2);
+ mMockLogger.closeLog();
mMockLogRegistry.dumpToGlobalLog(mMockLogger);
replayMocks(test, mockRescheduler);
mTestInvocation.invoke(mStubInvocationMetadata, mStubConfiguration, mockRescheduler);
@@ -1149,9 +1124,12 @@
mMockTestListener.invocationFailed(EasyMock.<FailureDescription>anyObject());
mMockSummaryListener.invocationFailed(EasyMock.<FailureDescription>anyObject());
} else {
+ FailureStatus failureStatus = FailureStatus.INFRA_FAILURE;
+ if (throwable instanceof BuildError) {
+ failureStatus = FailureStatus.DEPENDENCY_ISSUE;
+ }
FailureDescription failure =
- FailureDescription.create(
- throwable.getMessage(), FailureStatus.INFRA_FAILURE)
+ FailureDescription.create(throwable.getMessage(), failureStatus)
.setCause(throwable);
if (throwable instanceof BuildRetrievalError) {
failure.setActionInProgress(ActionInProgress.FETCHING_ARTIFACTS);
diff --git a/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java b/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java
index 7ce1e0d..d7a9b68 100644
--- a/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java
+++ b/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java
@@ -89,7 +89,7 @@
Object value0 = invocation(() -> local.get());
Object value1 = invocation(() -> local.get());
- assertThat(value0).isNotSameAs(value1);
+ assertThat(value0).isNotSameInstanceAs(value1);
}
/**
diff --git a/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java b/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java
index 91b8e71..47a791f 100644
--- a/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java
+++ b/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.tradefed.build.BuildInfo;
import com.android.tradefed.build.StubBuildProvider;
@@ -47,6 +48,7 @@
import com.android.tradefed.testtype.suite.ITestSuite;
import com.android.tradefed.util.FileUtil;
+import java.util.Arrays;
import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Before;
@@ -256,11 +258,36 @@
}
}
+ public class FakeStrictShardHelper extends StrictShardHelper {
+ List<IRemoteTest> fakeModules = new ArrayList<>();
+
+ public FakeStrictShardHelper(List<IRemoteTest> modules) {
+ fakeModules.addAll(modules);
+ }
+
+ @Override
+ protected List<List<IRemoteTest>> splitTests(List<IRemoteTest> fullList, int shardCount) {
+ List<List<IRemoteTest>> shards = new ArrayList<>();
+ shards.add(new ArrayList<>(fakeModules));
+ shards.add(new ArrayList<>(fakeModules));
+ return shards;
+ }
+ }
+
private ITestSuite createFakeSuite(String name) throws Exception {
ITestSuite suite = new SplitITestSuite(name);
return suite;
}
+ private ITestSuite createFakeSuite(String name, boolean intraModuleSharding) throws Exception {
+ ITestSuite suite = new SplitITestSuite(name);
+ if (!intraModuleSharding) {
+ OptionSetter setter = new OptionSetter(suite);
+ setter.setOptionValue("intra-module-sharding", "false");
+ }
+ return suite;
+ }
+
private List<IRemoteTest> testShard(int shardIndex) throws Exception {
mContext.addAllocatedDevice("default", EasyMock.createMock(ITestDevice.class));
List<IRemoteTest> test = new ArrayList<>();
@@ -282,6 +309,26 @@
return mConfig.getTests();
}
+ private List<IRemoteTest> createITestSuiteList(List<String> modules) throws Exception {
+ List<IRemoteTest> tests = new ArrayList<>();
+ for (String name : modules) {
+ tests.add(createFakeSuite(name, false).split(2, mTestInfo).iterator().next());
+ }
+
+ CommandOptions options = new CommandOptions();
+ OptionSetter setter = new OptionSetter(options);
+ setter.setOptionValue("shard-count", "2");
+ setter.setOptionValue("shard-index", Integer.toString(1));
+ setter.setOptionValue("optimize-mainline-test", "true");
+ mConfig.setCommandOptions(options);
+ mConfig.setCommandLine(new String[] {"empty"});
+ mConfig.setTests(tests);
+
+ FakeStrictShardHelper fakeHelper = new FakeStrictShardHelper(tests);
+ fakeHelper.shardConfig(mConfig, mTestInfo, mRescheduler, null);
+ return mConfig.getTests();
+ }
+
/**
* Total for all the _shardX test should be 14 tests (2 per modules). 6 for module1: 3 module1
* shard * 2 4 for module2: 2 module2 shard * 2 4 for module3: 2 module3 shard * 2
@@ -304,6 +351,61 @@
assertEquals(1, ((ITestSuite) res.get(2)).getDirectModule().numTests());
}
+ /**
+ * Test that the unsorted test modules are re-ordered.
+ */
+ @Test
+ public void testReorderTestModules() throws Exception {
+ List<String> unSortedModules =
+ Arrays.asList(
+ "module1[com.android.mod1.apex]",
+ "module1[com.android.mod1.apex+com.android.mod2.apex]",
+ "module2[com.android.mod1.apex]",
+ "module1[com.android.mod3.apk]",
+ "module2[com.android.mod1.apex+com.android.mod2.apex]",
+ "module2[com.android.mod3.apk]",
+ "module3[com.android.mod1.apex+com.android.mod2.apex]",
+ "module3[com.android.mod3.apk]",
+ "module4[com.android.mod3.apk]",
+ "module5[com.android.mod3.apk]"
+ );
+ List<IRemoteTest> res = createITestSuiteList(unSortedModules);
+
+ List<String> sortedModules =
+ Arrays.asList(
+ "module1[com.android.mod1.apex]",
+ "module2[com.android.mod1.apex]",
+ "module1[com.android.mod1.apex+com.android.mod2.apex]",
+ "module2[com.android.mod1.apex+com.android.mod2.apex]",
+ "module3[com.android.mod1.apex+com.android.mod2.apex]",
+ "module1[com.android.mod3.apk]",
+ "module2[com.android.mod3.apk]",
+ "module3[com.android.mod3.apk]",
+ "module4[com.android.mod3.apk]",
+ "module5[com.android.mod3.apk]"
+ );
+ for (int i = 0 ; i < sortedModules.size() ; i++) {
+ assertEquals(sortedModules.get(i), ((ITestSuite)res.get(i)).getDirectModule().getId());
+ }
+ }
+
+ /**
+ * Test that the there exist a module with invalid parameterized modules defined.
+ */
+ @Test
+ public void testReorderTestModulesWithUnexpectedMainlineModules() throws Exception {
+ List<String> modules = Arrays.asList("module1[com.mod1.apex]", "module1[com.mod1]");
+ try {
+ List<IRemoteTest> res = createITestSuiteList(modules);
+ fail("Should have thrown an exception.");
+ } catch (RuntimeException expected) {
+ // expected
+ assertTrue(expected.getMessage().contains(
+ "Module: module1[com.mod1] doesn't match the pattern for mainline " +
+ "modules. The pattern should end with apk/apex/apks."));
+ }
+ }
+
@Test
public void testMergeSuite_shard1() throws Exception {
List<IRemoteTest> res = testShard(1);
diff --git a/tests/src/com/android/tradefed/monitoring/LabResourceDeviceMonitorTest.java b/tests/src/com/android/tradefed/monitoring/LabResourceDeviceMonitorTest.java
new file mode 100644
index 0000000..ee51dfd
--- /dev/null
+++ b/tests/src/com/android/tradefed/monitoring/LabResourceDeviceMonitorTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 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.monitoring;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LabResourceDeviceMonitorTest {
+
+ private LabResourceDeviceMonitor mLabResourceDeviceMonitor;
+
+ @Before
+ public void setUp() {
+ mLabResourceDeviceMonitor = new LabResourceDeviceMonitor();
+ }
+
+ @Test
+ public void testServerStartAndShutdown() {
+ Assert.assertFalse(
+ "server should be empty before monitor run",
+ mLabResourceDeviceMonitor.getServer().isPresent());
+ mLabResourceDeviceMonitor.run();
+ Assert.assertTrue(
+ "server should present after monitor run",
+ mLabResourceDeviceMonitor.getServer().isPresent());
+ Assert.assertEquals(
+ LabResourceDeviceMonitor.DEFAULT_PORT,
+ mLabResourceDeviceMonitor.getServer().get().getPort());
+ mLabResourceDeviceMonitor.stop();
+ Assert.assertTrue(
+ "server should be shutdown after monitor stop",
+ mLabResourceDeviceMonitor.getServer().get().isShutdown());
+ }
+}
diff --git a/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java b/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
index d83c343..82fc304 100644
--- a/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
+++ b/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
@@ -306,10 +306,11 @@
}
/**
- * Test metrics enabled with key prefixing.
+ * Test metrics enabled with key and string value prefixing.
*/
@Test
- public void testParsingWithKeyPrefixing() throws ConfigurationException, IOException {
+ public void testParsingWithKeyAndStringValuePrefixing()
+ throws ConfigurationException, IOException {
setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
mOptionSetter.setOptionValue(KEY_PREFIX_OPTION,
@@ -320,8 +321,8 @@
PREFIX_OPTION_VALUE,
new LogFile(
perfettoMetricProtoFile.getAbsolutePath(), "some.url", LogDataType.TEXTPB));
- Map<String, Metric.Builder> parsedMetrics =
- mProcessor.processRunMetricsAndLogs(new HashMap<>(), testLogs);
+ Map<String, Metric.Builder> parsedMetrics = mProcessor
+ .processRunMetricsAndLogs(new HashMap<>(), testLogs);
assertMetricsContain(parsedMetrics,
"perfetto_android_hwui_metric-process_info-process_name-com.android.systemui-all_mem_min",
@@ -329,6 +330,28 @@
}
+ /**
+ * Test metrics enabled with key and integer value prefixing.
+ */
+ @Test
+ public void testParsingWithKeyAndIntegerValuePrefixing()
+ throws ConfigurationException, IOException {
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ mOptionSetter.setOptionValue(KEY_PREFIX_OPTION,
+ "perfetto.protos.AndroidCpuMetric.CoreData.id");
+ mOptionSetter.setOptionValue(ALL_METRICS_OPTION, "true");
+ Map<String, LogFile> testLogs = new HashMap<>();
+ testLogs.put(
+ PREFIX_OPTION_VALUE,
+ new LogFile(
+ perfettoMetricProtoFile.getAbsolutePath(), "some.url", LogDataType.TEXTPB));
+ Map<String, Metric.Builder> parsedMetrics = mProcessor
+ .processRunMetricsAndLogs(new HashMap<>(), testLogs);
+ assertMetricsContain(parsedMetrics, "perfetto_android_cpu-process_info-name-com.google."
+ + "android.apps.messaging-threads-name-BG Thread #1-core-id-1-metrics-runtime_ns",
+ 14376405);
+ }
/** Test the post processor can parse binary perfetto metric proto format. */
@Test
@@ -515,6 +538,54 @@
" all_mem_min: 15120269\n" +
" all_mem_avg: 24468104.289592762\n" +
" }\n" +
+ "}"
+ + "android_cpu {\n" +
+ " process_info {\n" +
+ " name: \"com.google.android.apps.messaging\"\n" +
+ " metrics {\n" +
+ " mcycles: 139\n" +
+ " runtime_ns: 639064902\n" +
+ " min_freq_khz: 576000\n" +
+ " max_freq_khz: 2016000\n" +
+ " avg_freq_khz: 324000\n" +
+ " }\n" +
+ " threads {\n" +
+ " name: \"BG Thread #1\"\n" +
+ " core {\n" +
+ " id: 0\n" +
+ " metrics {\n" +
+ " runtime_ns: 8371202\n" +
+ " }\n" +
+ " }\n" +
+ " core {\n" +
+ " id: 1\n" +
+ " metrics {\n" +
+ " mcycles: 0\n" +
+ " runtime_ns: 14376405\n" +
+ " min_freq_khz: 1785600\n" +
+ " max_freq_khz: 1785600\n" +
+ " avg_freq_khz: 57977\n" +
+ " }\n" +
+ " }\n" +
+ " metrics {\n" +
+ " mcycles: 0\n" +
+ " runtime_ns: 22747607\n" +
+ " min_freq_khz: 1785600\n" +
+ " max_freq_khz: 1785600\n" +
+ " avg_freq_khz: 36000\n" +
+ " }\n" +
+ " core_type {\n" +
+ " type: \"little\"\n" +
+ " metrics {\n" +
+ " mcycles: 0\n" +
+ " runtime_ns: 22747607\n" +
+ " min_freq_khz: 1785600\n" +
+ " max_freq_khz: 1785600\n" +
+ " avg_freq_khz: 36000\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
"}";
FileWriter fileWriter = null;
try {
@@ -593,3 +664,4 @@
.getSingleString())));
}
}
+
diff --git a/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java b/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java
index ff0692e..852a4df 100644
--- a/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java
+++ b/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java
@@ -80,6 +80,8 @@
"com.android.tradefed.testtype.rust.RustBinaryTest",
"com.android.tradefed.testtype.StubTest",
"com.android.tradefed.testtype.ArtRunTest",
+ "com.android.tradefed.testtype.ArtGTest",
+ "com.android.tradefed.testtype.mobly.MoblyBinaryHostTest",
// Others
"com.google.android.deviceconfig.RebootTest"));
diff --git a/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java b/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java
index a902fd2..583a4c0 100644
--- a/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java
+++ b/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java
@@ -19,6 +19,7 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import org.easymock.Capture;
import org.easymock.EasyMock;
@@ -52,7 +53,9 @@
mMockListener.testStarted(test, 0L);
EasyMock.expect(mMockDevice.getLogcatSince(0L))
.andReturn(new ByteArrayInputStreamSource("".getBytes()));
- mMockListener.testFailed(test, "instrumentation failed. reason: 'Process crashed.'");
+ mMockListener.testFailed(
+ test,
+ FailureDescription.create("instrumentation failed. reason: 'Process crashed.'"));
mMockListener.testEnded(test, 5L, new HashMap<String, Metric>());
EasyMock.replay(mMockListener, mMockDevice);
@@ -89,15 +92,12 @@
EasyMock.expect(mMockDevice.getLogcatSince(0L))
.andReturn(new ByteArrayInputStreamSource(logcat.getBytes()));
// Some crash was added to the failure
- mMockListener.testFailed(
- EasyMock.eq(test),
- EasyMock.contains(
- "instrumentation failed. reason: 'Process crashed.'"
- + "\nCrash Message:Runtime"));
+ Capture<FailureDescription> captured_1 = new Capture<>();
+ mMockListener.testFailed(EasyMock.eq(test), EasyMock.capture(captured_1));
mMockListener.testEnded(test, 5L, new HashMap<String, Metric>());
// If a run failure follows, expect it to contain the additional stack too.
- Capture<FailureDescription> captured = new Capture<>();
- mMockListener.testRunFailed(EasyMock.capture(captured));
+ Capture<FailureDescription> captured_2 = new Capture<>();
+ mMockListener.testRunFailed(EasyMock.capture(captured_2));
EasyMock.replay(mMockListener, mMockDevice);
mReporter.testStarted(test, 0L);
@@ -106,7 +106,16 @@
mReporter.testRunFailed("Something went wrong.");
EasyMock.verify(mMockListener, mMockDevice);
assertTrue(
- captured.getValue()
+ captured_1
+ .getValue()
+ .getErrorMessage()
+ .contains(
+ "instrumentation failed. reason: 'Process crashed.'"
+ + "\nCrash Message:Runtime"));
+ assertTrue(FailureStatus.TEST_FAILURE.equals(captured_1.getValue().getFailureStatus()));
+ assertTrue(
+ captured_2
+ .getValue()
.getErrorMessage()
.contains("Something went wrong.\nCrash Message:Runtime"));
}
@@ -138,7 +147,7 @@
EasyMock.expect(mMockDevice.getLogcatSince(0L))
.andReturn(new ByteArrayInputStreamSource(logcat.getBytes()));
// No crash added at the point of testFailed.
- mMockListener.testFailed(test, "Something went wrong.");
+ mMockListener.testFailed(test, FailureDescription.create("Something went wrong."));
mMockListener.testEnded(test, 5L, new HashMap<String, Metric>());
// If a run failure comes with a crash detected, expect it to contain the additional stack.
Capture<FailureDescription> captured = new Capture<>();
@@ -186,7 +195,7 @@
EasyMock.expect(mMockDevice.getLogcatSince(0L))
.andReturn(new ByteArrayInputStreamSource(logcat.getBytes()));
// No crash added at the point of testFailed.
- mMockListener.testFailed(test, "Something went wrong.");
+ mMockListener.testFailed(test, FailureDescription.create("Something went wrong."));
mMockListener.testEnded(test, 5L, new HashMap<String, Metric>());
// If a run failure comes with a crash detected, expect it to contain the additional stack.
Capture<FailureDescription> captured = new Capture<>();
@@ -207,4 +216,56 @@
+ "\tat class.method1(Class.java:1)\n"
+ "\tat class.method2(Class.java:2)\n"));
}
+
+ /** Test that test-timeout tests have failure status TIMED_OUT. */
+ @Test
+ @SuppressWarnings("MustBeClosedChecker")
+ public void testTestTimedOutTests() {
+ String trace =
+ "org.junit.runners.model.TestTimedOutException: "
+ + "test timed out after 1000 milliseconds";
+ mReporter = new LogcatCrashResultForwarder(mMockDevice, mMockListener);
+ TestDescription test = new TestDescription("com.class", "test");
+
+ mMockListener.testStarted(test, 0L);
+
+ Capture<FailureDescription> captured = new Capture<>();
+ mMockListener.testFailed(EasyMock.eq(test), EasyMock.capture(captured));
+ mMockListener.testEnded(test, 5L, new HashMap<String, Metric>());
+
+ EasyMock.replay(mMockListener, mMockDevice);
+ mReporter.testStarted(test, 0L);
+ mReporter.testFailed(test, trace);
+ mReporter.testEnded(test, 5L, new HashMap<String, Metric>());
+ EasyMock.verify(mMockListener, mMockDevice);
+ assertTrue(captured.getValue().getErrorMessage().contains(trace));
+ assertTrue(FailureStatus.TIMED_OUT.equals(captured.getValue().getFailureStatus()));
+ }
+
+ /** Test that shell-timeout tests have failure status TIMED_OUT. */
+ @Test
+ @SuppressWarnings("MustBeClosedChecker")
+ public void testShellTimedOutTests() {
+ String trace =
+ "Test failed to run to completion. "
+ + " Reason: 'Failed to receive adb shell test output within 3000 ms. "
+ + "Test may have timed out, or adb connection to device became "
+ + "unresponsive'. Check device logcat for details";
+ mReporter = new LogcatCrashResultForwarder(mMockDevice, mMockListener);
+ TestDescription test = new TestDescription("com.class", "test");
+
+ mMockListener.testStarted(test, 0L);
+
+ Capture<FailureDescription> captured = new Capture<>();
+ mMockListener.testFailed(EasyMock.eq(test), EasyMock.capture(captured));
+ mMockListener.testEnded(test, 5L, new HashMap<String, Metric>());
+
+ EasyMock.replay(mMockListener, mMockDevice);
+ mReporter.testStarted(test, 0L);
+ mReporter.testFailed(test, trace);
+ mReporter.testEnded(test, 5L, new HashMap<String, Metric>());
+ EasyMock.verify(mMockListener, mMockDevice);
+ assertTrue(captured.getValue().getErrorMessage().contains(trace));
+ assertTrue(FailureStatus.TIMED_OUT.equals(captured.getValue().getFailureStatus()));
+ }
}
diff --git a/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java b/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java
index 63556ab..b7cc8eb 100644
--- a/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java
+++ b/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java
@@ -37,6 +37,7 @@
List<ErrorIdentifier> errors = new ArrayList<>();
errors.addAll(Arrays.asList(InfraErrorIdentifier.values()));
errors.addAll(Arrays.asList(DeviceErrorIdentifier.values()));
+ errors.addAll(Arrays.asList(TestErrorIdentifier.values()));
List<String> names = errors.stream().map(e -> e.name()).collect(Collectors.toList());
Set<String> uniques = new HashSet<>();
diff --git a/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java b/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java
index 43aaa7a..bd17c87 100644
--- a/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java
+++ b/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java
@@ -211,6 +211,135 @@
}
@Test
+ public void testForwarding_assumptionFailure() {
+ mDetailedListener = EasyMock.createStrictMock(ITestDetailedReceiver.class);
+ LogFile test1Log = new LogFile("test1", "url", LogDataType.TEXT);
+ LogFile test2LogBefore = new LogFile("test2-before", "url", LogDataType.TEXT);
+ LogFile test2LogAfter = new LogFile("test2-after", "url", LogDataType.TEXT);
+ LogFile testRun1LogBefore = new LogFile("test-run1-before", "url", LogDataType.TEXT);
+ LogFile testRun1LogAfter = new LogFile("test-run1-after", "url", LogDataType.TEXT);
+ LogFile beforeEnd = new LogFile("path", "url", LogDataType.TEXT);
+ LogFile betweenAttemptsLog = new LogFile("between-attempts", "url", LogDataType.TEXT);
+ LogFile moduleLog = new LogFile("module-log", "url", LogDataType.TEXT);
+ TestDescription test1 = new TestDescription("classname", "test1");
+ TestDescription test2 = new TestDescription("classname", "test2");
+ ILogSaver logger = EasyMock.createMock(ILogSaver.class);
+
+ EasyMock.expect(mDetailedListener.supportGranularResults()).andStubReturn(true);
+
+ // Invocation level
+ mAggListener.setLogSaver(logger);
+ mAggListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mAggListener.getSummary()).andStubReturn(null);
+ mDetailedListener.setLogSaver(logger);
+ mDetailedListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mDetailedListener.getSummary()).andStubReturn(null);
+
+ mAggListener.testModuleStarted(mModuleContext);
+ mDetailedListener.testModuleStarted(mModuleContext);
+
+ // Detailed receives the breakdown
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mDetailedListener.logAssociation("test1-log", test1Log);
+ mDetailedListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.logAssociation("test2-before-log", test2LogBefore);
+ mDetailedListener.testFailed(test2, FailureDescription.create("I failed. retry me."));
+ mDetailedListener.logAssociation("test2-after-log", test2LogAfter);
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.logAssociation("test-run1-before-log", testRun1LogBefore);
+ mDetailedListener.logAssociation("test-run1-after-log", testRun1LogAfter);
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(1), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testAssumptionFailure(
+ EasyMock.eq(test2), EasyMock.eq(FailureDescription.create("Assump failure")));
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.logAssociation("between-attempts", betweenAttemptsLog);
+ mDetailedListener.logAssociation("module-log", moduleLog);
+
+ // Aggregated listeners receives the aggregated results
+ mAggListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mAggListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mAggListener.logAssociation("test1-log", test1Log);
+ mAggListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mAggListener.testAssumptionFailure(
+ EasyMock.eq(test2), (FailureDescription) EasyMock.anyObject());
+ mAggListener.logAssociation("test2-before-log", test2LogBefore);
+ mAggListener.logAssociation("test2-after-log", test2LogAfter);
+ mAggListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.logAssociation("test-run1-before-log", testRun1LogBefore);
+ mAggListener.logAssociation("test-run1-after-log", testRun1LogAfter);
+ mAggListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mAggListener.logAssociation("between-attempts", betweenAttemptsLog);
+ mAggListener.logAssociation("module-log", moduleLog);
+ mAggListener.testModuleEnded();
+ mDetailedListener.testModuleEnded();
+ mAggListener.logAssociation("before-end", beforeEnd);
+ mAggListener.invocationEnded(500L);
+ mDetailedListener.logAssociation("before-end", beforeEnd);
+ mDetailedListener.invocationEnded(500L);
+
+ EasyMock.replay(mAggListener, mDetailedListener);
+ mAggregator =
+ new TestableResultAggregator(
+ Arrays.asList(mAggListener, mDetailedListener),
+ RetryStrategy.RETRY_ANY_FAILURE);
+ mAggregator.setLogSaver(logger);
+ mAggregator.invocationStarted(mInvocationContext);
+ mAggregator.testModuleStarted(mModuleContext);
+ // Attempt 1
+ mAggregator.testRunStarted("run1", 2, 0);
+ mAggregator.testStarted(test1);
+ mAggregator.logAssociation("test1-log", test1Log);
+ mAggregator.testEnded(test1, new HashMap<String, Metric>());
+ mAggregator.testStarted(test2);
+ mAggregator.logAssociation("test2-before-log", test2LogBefore);
+ mAggregator.testFailed(test2, FailureDescription.create("I failed. retry me."));
+ mAggregator.logAssociation("test2-after-log", test2LogAfter);
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.logAssociation("test-run1-before-log", testRun1LogBefore);
+ mAggregator.testRunFailed("run fail");
+ mAggregator.logAssociation("test-run1-after-log", testRun1LogAfter);
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ mAggregator.logAssociation("between-attempts", betweenAttemptsLog);
+ // Attempt 2
+ mAggregator.testRunStarted("run1", 2, 1);
+ mAggregator.testStarted(test2);
+ mAggregator.testAssumptionFailure(test2, FailureDescription.create("Assump failure"));
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggregator.logAssociation("module-log", moduleLog);
+ mAggregator.testModuleEnded();
+ mAggregator.logAssociation("before-end", beforeEnd);
+ mAggregator.invocationEnded(500L);
+ EasyMock.verify(mAggListener, mDetailedListener);
+ assertEquals("run fail", mAggregator.getInvocationMetricRunError());
+ }
+
+ @Test
public void testForwarding_runFailure() {
mDetailedListener = EasyMock.createStrictMock(ITestDetailedReceiver.class);
TestDescription test1 = new TestDescription("classname", "test1");
diff --git a/tests/src/com/android/tradefed/sandbox/SandboxedInvocationExecutionTest.java b/tests/src/com/android/tradefed/sandbox/SandboxedInvocationExecutionTest.java
deleted file mode 100644
index 7594ce4..0000000
--- a/tests/src/com/android/tradefed/sandbox/SandboxedInvocationExecutionTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 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.sandbox;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import com.android.tradefed.build.BuildInfo;
-import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.Configuration;
-import com.android.tradefed.config.ConfigurationDef;
-import com.android.tradefed.config.IConfiguration;
-import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
-import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.invoker.sandbox.SandboxedInvocationExecution;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.File;
-
-/** Unit tests for {@link SandboxedInvocationExecution}. */
-@RunWith(JUnit4.class)
-public class SandboxedInvocationExecutionTest {
-
- private SandboxedInvocationExecution mExecution;
- private IInvocationContext mContext;
- private IConfiguration mConfig;
-
- @Before
- public void setUp() {
- mExecution = new SandboxedInvocationExecution();
- mContext = new InvocationContext();
- mConfig = new Configuration("name", "desc");
- mConfig.getConfigurationDescription().setSandboxed(true);
- }
-
- @Test
- public void testBuildInfo_testTag() throws Exception {
- IBuildInfo info = new BuildInfo();
- assertEquals("stub", info.getTestTag());
- info.setFile(BuildInfoFileKey.TESTDIR_IMAGE, new File("doesnt_matter_testsdir"), "tests");
- mContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, info);
- mConfig.getCommandOptions().setTestTag("test");
- TestInformation testInfo =
- TestInformation.newBuilder().setInvocationContext(mContext).build();
- assertNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY));
- mExecution.fetchBuild(testInfo, mConfig, null, null);
- // Build test tag was updated
- assertEquals("test", info.getTestTag());
- // Execution file was back filled
- assertNotNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY));
- }
-}
diff --git a/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java b/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java
index a32f9b6..d58167f 100644
--- a/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java
+++ b/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java
@@ -266,6 +266,22 @@
EasyMock.verify(mMockDevice);
}
+ public void testSetup_wifi_network_empty() throws Exception {
+ doSetupExpectations();
+ doCheckExternalStoreSpaceExpectations();
+ doSettingExpectations("global", "wifi_on", "1");
+ doCommandsExpectations("svc wifi enable");
+ EasyMock.replay(mMockDevice);
+
+ mDeviceSetup.setWifiNetwork("");
+ mDeviceSetup.setWifiPsk("psk");
+
+ mDeviceSetup.setWifi(BinaryState.ON);
+ mDeviceSetup.setUp(mTestInfo);
+
+ EasyMock.verify(mMockDevice);
+ }
+
public void testSetup_wifi_multiple_network_names() throws Exception {
doSetupExpectations();
doCheckExternalStoreSpaceExpectations();
@@ -861,6 +877,9 @@
doCheckExternalStoreSpaceExpectations();
EasyMock.expect(mMockDevice.setProperty("persist.sys.timezone", "America/Los_Angeles"))
.andReturn(true);
+ EasyMock.expect(mMockDevice.getProperty("persist.sys.timezone"))
+ .andReturn("America/Los_Angeles")
+ .anyTimes();
EasyMock.replay(mMockDevice);
mDeviceSetup.setTimezone("America/Los_Angeles");
@@ -963,6 +982,9 @@
Capture<String> setPropCapture = new Capture<>();
doSetupExpectations(true, setPropCapture);
doCheckExternalStoreSpaceExpectations();
+ EasyMock.expect(mMockDevice.getProperty("dalvik.vm.dexopt-flags"))
+ .andReturn("v=n")
+ .anyTimes();
EasyMock.replay(mMockDevice);
mDeviceSetup.setDisableDalvikVerifier(true);
@@ -1031,6 +1053,7 @@
Capture<String> setPropCapture = new Capture<>();
doSetupExpectations(true, setPropCapture);
doCheckExternalStoreSpaceExpectations();
+ EasyMock.expect(mMockDevice.getProperty("key")).andReturn("value").anyTimes();
EasyMock.replay(mMockDevice);
mDeviceSetup.setDeprecatedAudioSilent(false);
@@ -1104,6 +1127,7 @@
EasyMock.expect(mMockDevice.pushFile(f, "/data/local.prop")).andReturn(true).once();
mMockDevice.reboot();
EasyMock.expectLastCall().once();
+ EasyMock.expect(mMockDevice.getProperty("key")).andReturn("value").anyTimes();
EasyMock.replay(mMockDevice);
@@ -1122,6 +1146,7 @@
mMockDevice.deleteFile("/data/local.prop");
mMockDevice.reboot();
EasyMock.expectLastCall().once();
+ EasyMock.expect(mMockDevice.getProperty("key")).andReturn("value").anyTimes();
EasyMock.replay(mMockDevice);
@@ -1342,7 +1367,16 @@
if (testHarness) {
EasyMock.expect(mMockDevice.setProperty("persist.sys.test_harness", "1"))
.andReturn(true);
+ EasyMock.expect(mMockDevice.getProperty("persist.sys.test_harness"))
+ .andReturn("1")
+ .anyTimes();
}
+ EasyMock.expect(mMockDevice.getProperty("ro.telephony.disable-call"))
+ .andReturn("true")
+ .anyTimes();
+ EasyMock.expect(mMockDevice.getProperty("ro.audio.silent")).andReturn("1").anyTimes();
+ EasyMock.expect(mMockDevice.getProperty("ro.test_harness")).andReturn("1").anyTimes();
+ EasyMock.expect(mMockDevice.getProperty("ro.monkey")).andReturn("1").anyTimes();
}
/**
diff --git a/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java b/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java
index 9b38ce6..e889441 100644
--- a/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java
@@ -28,10 +28,9 @@
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.ZipUtil;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
+
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,6 +39,10 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
/** Unit tests for {@link DynamicSystemPreparer}. */
@RunWith(JUnit4.class)
public class DynamicSystemPreparerTest {
@@ -95,15 +98,12 @@
}
}
- @Test
- public void testSetUp() throws TargetSetupError, BuildError, DeviceNotAvailableException {
- Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz")))
- .thenReturn(Boolean.TRUE);
+ private void mockGsiToolStatus(String status) throws DeviceNotAvailableException {
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) {
- byte[] outputBytes = "running".getBytes();
+ byte[] outputBytes = status.getBytes();
((CollectingOutputReceiver) invocation.getArguments()[1])
.addOutput(outputBytes, 0, outputBytes.length);
return null;
@@ -112,10 +112,62 @@
.when(mMockDevice)
.executeShellCommand(
matches("gsi_tool status"), any(CollectingOutputReceiver.class));
+ }
+
+ @Test
+ public void testSetUp() throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz")))
+ .thenReturn(Boolean.TRUE);
+ Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(true);
+ mockGsiToolStatus("running");
CommandResult res = new CommandResult();
res.setStdout("");
res.setStatus(CommandStatus.SUCCESS);
Mockito.when(mMockDevice.executeShellV2Command("gsi_tool enable")).thenReturn(res);
mPreparer.setUp(mMockDevice, mBuildInfo);
}
+
+ @Test
+ public void testSetUp_installationFail() throws BuildError, DeviceNotAvailableException {
+ Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz")))
+ .thenReturn(Boolean.TRUE);
+ Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(false);
+ try {
+ mPreparer.setUp(mMockDevice, mBuildInfo);
+ Assert.fail("setUp() should have thrown.");
+ } catch (TargetSetupError e) {
+ Assert.assertEquals(
+ "Timed out waiting for DSU installation to complete and reboot",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetUp_rebootFail() throws BuildError, DeviceNotAvailableException {
+ Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz")))
+ .thenReturn(Boolean.TRUE);
+ Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(true);
+ Mockito.doThrow(new DeviceNotAvailableException()).when(mMockDevice).waitForDeviceOnline();
+ try {
+ mPreparer.setUp(mMockDevice, mBuildInfo);
+ Assert.fail("setUp() should have thrown.");
+ } catch (TargetSetupError e) {
+ Assert.assertEquals("Timed out booting into DSU", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetUp_noDsuRunningAfterRebootFail()
+ throws BuildError, DeviceNotAvailableException {
+ Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz")))
+ .thenReturn(Boolean.TRUE);
+ Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(true);
+ mockGsiToolStatus("normal");
+ try {
+ mPreparer.setUp(mMockDevice, mBuildInfo);
+ Assert.fail("setUp() should have thrown.");
+ } catch (TargetSetupError e) {
+ Assert.assertEquals("Failed to boot into DSU", e.getMessage());
+ }
+ }
}
diff --git a/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java b/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java
index f91253e..bccf68d 100644
--- a/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java
@@ -178,21 +178,36 @@
EasyMock.verify(mMockDevice, mMockRunUtil);
}
- /* Verifies that setUp will throw exception when there is no vbmeta.img in the zip file*/
+ /* Verifies that setUp can pass when there is no vbmeta.img is provided*/
@Test
- public void testSetUp_NoVbmetaImageInGsiZip() throws Exception {
+ public void testSetUp_Success_NoVbmetaImage() throws Exception {
File gsiDir = FileUtil.createTempDir("gsi_folder", mTmpDir);
File systemImg = new File(gsiDir, "system.img");
- File gsiZip = FileUtil.createTempFile("gsi_image", ".zip", mTmpDir);
- ZipUtil.createZip(List.of(systemImg), gsiZip);
- mBuildInfo.setFile("gsi_system.img", gsiZip, "0");
+ FileUtil.writeToFile("ddd", systemImg);
+ mBuildInfo.setFile("gsi_system.img", systemImg, "0");
+ mMockDevice.waitForDeviceOnline();
+ EasyMock.expect(mMockDevice.getApiLevel()).andReturn(29);
+ mMockDevice.rebootIntoBootloader();
+ mMockRunUtil.allowInterrupt(false);
+ mMockDevice.rebootIntoFastbootd();
+ doGetSlotExpectation();
+ EasyMock.expect(
+ mMockDevice.executeLongFastbootCommand(
+ "delete-logical-partition", "product_a"))
+ .andReturn(mSuccessResult);
+ EasyMock.expect(mMockDevice.executeLongFastbootCommand("erase", "system_a"))
+ .andReturn(mSuccessResult);
+ EasyMock.expect(
+ mMockDevice.executeLongFastbootCommand(
+ "flash",
+ "system",
+ mBuildInfo.getFile("gsi_system.img").getAbsolutePath()))
+ .andReturn(mSuccessResult);
+ EasyMock.expect(mMockDevice.executeLongFastbootCommand("-w")).andReturn(mSuccessResult);
+ mMockRunUtil.allowInterrupt(true);
+ doSetupExpectations();
EasyMock.replay(mMockDevice, mMockRunUtil);
- try {
- mPreparer.setUp(mTestInfo);
- fail("TargetSetupError is expected");
- } catch (TargetSetupError e) {
- // expected
- }
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice, mMockRunUtil);
}
diff --git a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
index 334ee6e..0652a21 100644
--- a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
@@ -24,6 +24,7 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.command.remote.DeviceDescriptor;
import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.ITestDevice.ApexInfo;
import com.android.tradefed.invoker.IInvocationContext;
@@ -35,6 +36,7 @@
import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
@@ -60,6 +62,8 @@
private TestInformation mTestInfo;
private BundletoolUtil mMockBundletoolUtil;
private File mFakeApex;
+ private File mFakeApex2;
+ private File mFakeApex3;
private File mFakeApk;
private File mFakeApk2;
private File mFakePersistentApk;
@@ -68,6 +72,8 @@
private File mBundletoolJar;
private OptionSetter mSetter;
private static final String APEX_PACKAGE_NAME = "com.android.FAKE_APEX_PACKAGE_NAME";
+ private static final String APEX2_PACKAGE_NAME = "com.android.FAKE_APEX2_PACKAGE_NAME";
+ private static final String APEX3_PACKAGE_NAME = "com.android.FAKE_APEX3_PACKAGE_NAME";
private static final String APK_PACKAGE_NAME = "com.android.FAKE_APK_PACKAGE_NAME";
private static final String APK2_PACKAGE_NAME = "com.android.FAKE_APK2_PACKAGE_NAME";
private static final String PERSISTENT_APK_PACKAGE_NAME = "com.android.PERSISTENT_PACKAGE_NAME";
@@ -78,6 +84,7 @@
private static final String APEX_PACKAGE_KEYWORD = "FAKE_APEX_PACKAGE_NAME";
private static final long APEX_VERSION = 1;
private static final String APEX_NAME = "fakeApex.apex";
+ private static final String APEX2_NAME = "fakeApex_2.apex";
private static final String APK_NAME = "fakeApk.apk";
private static final String APK2_NAME = "fakeSecondApk.apk";
private static final String PERSISTENT_APK_NAME = "fakePersistentApk.apk";
@@ -92,6 +99,8 @@
@Before
public void setUp() throws Exception {
mFakeApex = FileUtil.createTempFile("fakeApex", ".apex");
+ mFakeApex2 = FileUtil.createTempFile("fakeApex_2", ".apex");
+ mFakeApex3 = FileUtil.createTempFile("fakeApex_3", ".apex");
mFakeApk = FileUtil.createTempFile("fakeApk", ".apk");
mFakeApk2 = FileUtil.createTempFile("fakeSecondApk", ".apk");
mFakePersistentApk = FileUtil.createTempFile("fakePersistentApk", ".apk");
@@ -127,6 +136,12 @@
protected File getLocalPathForFilename(
TestInformation testInfo, String appFileName) throws TargetSetupError {
if (appFileName.endsWith(".apex")) {
+ if (appFileName.contains("fakeApex_2")) {
+ return mFakeApex2;
+ }
+ else if (appFileName.contains("fakeApex_3")) {
+ return mFakeApex3;
+ }
return mFakeApex;
}
if (appFileName.endsWith(".apk")) {
@@ -138,13 +153,11 @@
return mFakeApk;
}
}
- if (appFileName.endsWith(".apks")) {
- if (appFileName.contains("Apex")) {
- return mFakeApexApks;
- }
- if (appFileName.contains("Apk")) {
- return mFakeApkApks;
- }
+ if (SPLIT_APEX_APKS_NAME.equals(appFileName)) {
+ return mFakeApexApks;
+ }
+ if (SPLIT_APK__APKS_NAME.equals(appFileName)) {
+ return mFakeApkApks;
}
if (appFileName.endsWith(".jar")) {
return mBundletoolJar;
@@ -156,6 +169,12 @@
protected String parsePackageName(
File testAppFile, DeviceDescriptor deviceDescriptor) {
if (testAppFile.getName().endsWith(".apex")) {
+ if (testAppFile.getName().contains("fakeApex_2")) {
+ return APEX2_PACKAGE_NAME;
+ }
+ else if (testAppFile.getName().contains("fakeApex_3")) {
+ return APEX3_PACKAGE_NAME;
+ }
return APEX_PACKAGE_NAME;
}
if (testAppFile.getName().endsWith(".apk")
@@ -185,6 +204,8 @@
ApexInfo apexInfo;
if (apex.getName().contains("Split")) {
apexInfo = new ApexInfo(SPLIT_APEX_PACKAGE_NAME, APEX_VERSION);
+ } else if (apex.getName().contains("fakeApex_2")) {
+ apexInfo = new ApexInfo(APEX2_PACKAGE_NAME, APEX_VERSION);
} else {
apexInfo = new ApexInfo(APEX_PACKAGE_NAME, APEX_VERSION);
}
@@ -209,11 +230,343 @@
@After
public void tearDown() throws Exception {
FileUtil.deleteFile(mFakeApex);
+ FileUtil.deleteFile(mFakeApex2);
+ FileUtil.deleteFile(mFakeApex3);
FileUtil.deleteFile(mFakeApk);
FileUtil.deleteFile(mFakeApk2);
FileUtil.deleteFile(mFakePersistentApk);
}
+ /**
+ * Test that it gets the correct apex files that are already installed on the /data directory.
+ */
+ @Test
+ public void testGetApexInData() throws Exception {
+ Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
+ Set<ApexInfo> expectedApex = new HashSet<ApexInfo>();
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexSystem =
+ new ApexInfo(
+ "com.android.FAKE_APEX3_PACKAGE_NAME",
+ 1,
+ "/system/apex/com.android.FAKE_APEX3_PACKAGE_NAME@1.apex");
+
+ activatedApex = new HashSet<>(Arrays.asList(fakeApexData, fakeApexData2, fakeApexSystem));
+ expectedApex = new HashSet<>(Arrays.asList(fakeApexData, fakeApexData2));
+ assertEquals(2, mInstallApexModuleTargetPreparer.getApexInData(activatedApex).size());
+ assertEquals(expectedApex, mInstallApexModuleTargetPreparer.getApexInData(activatedApex));
+
+ activatedApex = new HashSet<>(Arrays.asList(fakeApexSystem));
+ assertEquals(0, mInstallApexModuleTargetPreparer.getApexInData(activatedApex).size());
+ }
+
+ /**
+ * Test that it returns the correct files to be installed and uninstalled.
+ */
+ @Test
+ public void testGetModulesToUninstall_NoneUninstallAndInstallFiles() throws Exception {
+ Set<ApexInfo> apexInData = new HashSet<>();
+ List<File> testFiles = new ArrayList<>();
+ testFiles.add(mFakeApex);
+ testFiles.add(mFakeApex2);
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ apexInData.add(fakeApexData);
+ apexInData.add(fakeApexData2);
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ Set<ApexInfo> results = mInstallApexModuleTargetPreparer.getModulesToUninstall(
+ apexInData, testFiles, mMockDevice);
+
+ assertEquals(0, testFiles.size());
+ assertEquals(0, results.size());
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ }
+
+ /**
+ * Test that it returns the correct files to be installed and uninstalled.
+ */
+ @Test
+ public void testGetModulesToUninstall_UninstallAndInstallFiles() throws Exception {
+ Set<ApexInfo> apexInData = new HashSet<>();
+ List<File> testFiles = new ArrayList<>();
+ testFiles.add(mFakeApex3);
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ apexInData.add(fakeApexData);
+ apexInData.add(fakeApexData2);
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ Set<ApexInfo> results = mInstallApexModuleTargetPreparer.getModulesToUninstall(
+ apexInData, testFiles, mMockDevice);
+ assertEquals(1, testFiles.size());
+ assertEquals(mFakeApex3, testFiles.get(0));
+ assertEquals(2, results.size());
+ results.containsAll(apexInData);
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ }
+
+ /**
+ * Test that it returns the correct files to be installed and uninstalled.
+ */
+ @Test
+ public void testGetModulesToUninstall_UninstallAndInstallFiles2() throws Exception {
+ Set<ApexInfo> apexInData = new HashSet<>();
+ List<File> testFiles = new ArrayList<>();
+ testFiles.add(mFakeApex2);
+ testFiles.add(mFakeApex3);
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ apexInData.add(fakeApexData);
+ apexInData.add(fakeApexData2);
+
+ EasyMock.replay(mMockDevice);
+ Set<ApexInfo> results =
+ mInstallApexModuleTargetPreparer.getModulesToUninstall(
+ apexInData, testFiles, mMockDevice);
+ assertEquals(1, testFiles.size());
+ assertEquals(mFakeApex3, testFiles.get(0));
+ assertEquals(1, results.size());
+ results.contains(fakeApexData);
+ EasyMock.verify(mMockDevice);
+ }
+
+ /**
+ * Test the method behaves the same process when the files to be installed contain apk or apks.
+ */
+ @Test
+ public void testSetupAndTearDown_Optimize_APEXANDAPK_Reboot() throws Exception {
+ mSetter.setOptionValue("skip-apex-teardown", "true");
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
+ mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME);
+
+ mMockDevice.deleteFile(APEX_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ CommandResult res = new CommandResult();
+ res.setStdout("test.apex");
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(new HashSet<>()).times(2);
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(
+ new HashSet<>(Arrays.asList(fakeApexData))).atLeastOnce();
+ mockSuccessfulInstallMultiPackageAndReboot();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APK_PACKAGE_NAME);
+ installableModules.add(APEX_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+ EasyMock.replay(mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ EasyMock.verify(mMockDevice);
+ }
+
+ /**
+ * Test the method will optimize the process and it will not reboot because the files to be
+ * installed are already installed on the device.
+ */
+ @Test
+ public void testSetupAndTearDown_Optimize_MultipleAPEX_NoReboot() throws Exception {
+ mSetter.setOptionValue("skip-apex-teardown", "true");
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME);
+
+ Set<ApexInfo> apexInData = new HashSet<>();
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ apexInData.add(fakeApexData);
+ apexInData.add(fakeApexData2);
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(apexInData).times(2);
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX_PACKAGE_NAME);
+ installableModules.add(APEX2_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+ EasyMock.replay(mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ EasyMock.verify(mMockDevice);
+ }
+
+ /**
+ * Test the method will uninstall the unused files and install the required files for the
+ * current test, and finally reboot the device.
+ */
+ @Test
+ public void testSetupAndTearDown_Optimize_MultipleAPEX_UninstallThenInstallAndReboot()
+ throws Exception {
+ mSetter.setOptionValue("skip-apex-teardown", "true");
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME);
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(
+ new HashSet<>(Arrays.asList(fakeApexData))).times(2);
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(
+ new HashSet<>(Arrays.asList(fakeApexData2))).atLeastOnce();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX2_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+ EasyMock.expect(mMockDevice.uninstallPackage(EasyMock.anyObject()))
+ .andReturn(null)
+ .once();
+ mockSuccessfulInstallPackageAndReboot(mFakeApex2);
+ EasyMock.replay(mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ EasyMock.verify(mMockDevice);
+ }
+
+ /**
+ * Test the method will uninstall the unused files for the current test, and finally reboot the
+ * device.
+ */
+ @Test
+ public void testSetupAndTearDown_Optimize_MultipleAPEX_UninstallAndReboot() throws Exception {
+ mSetter.setOptionValue("skip-apex-teardown", "true");
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME);
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(
+ new HashSet<>(Arrays.asList(fakeApexData, fakeApexData2))).times(2);
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX2_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+ EasyMock.expect(mMockDevice.uninstallPackage(EasyMock.anyObject()))
+ .andReturn(null)
+ .once();
+ mMockDevice.reboot();
+ EasyMock.expectLastCall().once();
+ EasyMock.replay(mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ EasyMock.verify(mMockDevice);
+ }
+
+ /**
+ * Test the method will install the required files for the current test, and finally reboot the
+ * device.
+ */
+ @Test
+ public void testSetupAndTearDown_Optimize_MultipleAPEX_Reboot() throws Exception {
+ mSetter.setOptionValue("skip-apex-teardown", "true");
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME);
+
+ Set<ApexInfo> apexInData = new HashSet<>();
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ apexInData.add(fakeApexData);
+ apexInData.add(fakeApexData2);
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(
+ new HashSet<>(Arrays.asList(fakeApexData))).times(2);
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(apexInData).atLeastOnce();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX_PACKAGE_NAME);
+ installableModules.add(APEX2_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+ mockSuccessfulInstallPackageAndReboot(mFakeApex2);
+ EasyMock.replay(mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ EasyMock.verify(mMockDevice);
+ }
+
@Test
public void testSetupSuccess_removeExistingStagedApexSuccess() throws Exception {
mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
@@ -304,6 +657,39 @@
EasyMock.verify(mMockBuildInfo, mMockDevice);
}
+ @Test
+ public void testSetupSuccess_withAbsoluteTestFileName() throws Exception {
+ mInstallApexModuleTargetPreparer.addTestFile(mFakeApex);
+ mMockDevice.deleteFile(APEX_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ CommandResult res = new CommandResult();
+ res.setStdout("test.apex");
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+ mockSuccessfulInstallPackageAndReboot(mFakeApex);
+ Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
+ activatedApex.add(
+ new ApexInfo(
+ "com.android.FAKE_APEX_PACKAGE_NAME",
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"));
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(activatedApex).times(3);
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ }
+
@Test(expected = TargetSetupError.class)
public void testSetupFail_getActivatedPackageSuccessThrowModuleNotPreloaded() throws Exception {
mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME);
@@ -748,19 +1134,7 @@
@Test
public void testSetupAndTearDown() throws Exception {
mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
- mMockDevice.deleteFile(APEX_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- CommandResult res = new CommandResult();
- res.setStdout("test.apex");
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
- mMockDevice.reboot();
- EasyMock.expectLastCall();
+ mockCleanInstalledApexPackagesAndReboot();
mockSuccessfulInstallPackageAndReboot(mFakeApex);
Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
activatedApex.add(
@@ -801,19 +1175,7 @@
public void testSetupAndTearDown_MultiInstall() throws Exception {
mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME);
- mMockDevice.deleteFile(APEX_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- CommandResult res = new CommandResult();
- res.setStdout("test.apex");
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
- mMockDevice.reboot();
- EasyMock.expectLastCall();
+ mockCleanInstalledApexPackagesAndReboot();
mockSuccessfulInstallMultiPackageAndReboot();
Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
activatedApex.add(
@@ -858,22 +1220,7 @@
mBundletoolJar = File.createTempFile("bundletool", ".jar");
File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks);
try {
- mMockDevice.deleteFile(APEX_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- CommandResult res = new CommandResult();
- res.setStdout("test.apex");
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR))
- .andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR))
- .andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR))
- .andReturn(res);
- mMockDevice.reboot();
- EasyMock.expectLastCall();
+ mockCleanInstalledApexPackagesAndReboot();
when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class)))
.thenReturn("serial.json");
@@ -965,6 +1312,340 @@
}
@Test
+ public void testInstallUsingBundletool_AbsolutePath() throws Exception {
+ mInstallApexModuleTargetPreparer.addTestFileName(SPLIT_APEX_APKS_NAME);
+ mInstallApexModuleTargetPreparer.addTestFileName(SPLIT_APK__APKS_NAME);
+ mFakeApexApks = File.createTempFile("fakeApex", ".apks");
+ mFakeApkApks = File.createTempFile("fakeApk", ".apks");
+
+ File fakeSplitApexApks = File.createTempFile("ApexSplits", "");
+ fakeSplitApexApks.delete();
+ fakeSplitApexApks.mkdir();
+ File splitApex = File.createTempFile("fakeSplitApex", ".apex", fakeSplitApexApks);
+
+ File fakeSplitApkApks = File.createTempFile("ApkSplits", "");
+ fakeSplitApkApks.delete();
+ fakeSplitApkApks.mkdir();
+ File splitApk1 = File.createTempFile("fakeSplitApk1", ".apk", fakeSplitApkApks);
+ mBundletoolJar = File.createTempFile("/fake/absolute/path/bundletool", ".jar");
+ File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks);
+ try {
+ mockCleanInstalledApexPackagesAndReboot();
+ when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class)))
+ .thenReturn("serial.json");
+
+ assertTrue(fakeSplitApexApks != null);
+ assertTrue(fakeSplitApkApks != null);
+ assertTrue(mFakeApexApks != null);
+ assertTrue(mFakeApkApks != null);
+ assertEquals(1, fakeSplitApexApks.listFiles().length);
+ assertEquals(2, fakeSplitApkApks.listFiles().length);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApexApks);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApkApks);
+
+ mMockDevice.waitForDeviceAvailable();
+
+ List<String> trainInstallCmd = new ArrayList<>();
+ trainInstallCmd.add("install-multi-package");
+ trainInstallCmd.add(splitApex.getAbsolutePath());
+ String cmd = "";
+ for (File f : fakeSplitApkApks.listFiles()) {
+ if (!cmd.isEmpty()) {
+ cmd += ":" + f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ } else {
+ cmd += f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ }
+ }
+ trainInstallCmd.add(cmd);
+ EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
+ .andReturn("Success")
+ .once();
+ mMockDevice.reboot();
+ Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
+ activatedApex.add(
+ new ApexInfo(
+ SPLIT_APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"));
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(activatedApex).times(3);
+ EasyMock.expect(mMockDevice.uninstallPackage(SPLIT_APK_PACKAGE_NAME))
+ .andReturn(null)
+ .once();
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX_PACKAGE_NAME);
+ installableModules.add(SPLIT_APK_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ Mockito.verify(mMockBundletoolUtil, times(1))
+ .generateDeviceSpecFile(Mockito.any(ITestDevice.class));
+ // Extract splits 1 time to get the package name for the module, and again during
+ // installation.
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ } finally {
+ FileUtil.deleteFile(mFakeApexApks);
+ FileUtil.deleteFile(mFakeApkApks);
+ FileUtil.recursiveDelete(fakeSplitApexApks);
+ FileUtil.deleteFile(fakeSplitApexApks);
+ FileUtil.recursiveDelete(fakeSplitApkApks);
+ FileUtil.deleteFile(fakeSplitApkApks);
+ FileUtil.deleteFile(mBundletoolJar);
+ }
+ }
+
+ @Test
+ public void testInstallUsingBundletool_TrainFolder() throws Exception {
+ File trainFolder = File.createTempFile("tmpTrain", "");
+ trainFolder.delete();
+ trainFolder.mkdir();
+ mSetter.setOptionValue("train-path", trainFolder.getAbsolutePath());
+ mFakeApexApks = File.createTempFile("fakeApex", ".apks", trainFolder);
+ mFakeApkApks = File.createTempFile("fakeApk", ".apks", trainFolder);
+
+ File fakeSplitApexApks = File.createTempFile("ApexSplits", "");
+ fakeSplitApexApks.delete();
+ fakeSplitApexApks.mkdir();
+ File splitApex = File.createTempFile("fakeSplitApex", ".apex", fakeSplitApexApks);
+
+ File fakeSplitApkApks = File.createTempFile("ApkSplits", "");
+ fakeSplitApkApks.delete();
+ fakeSplitApkApks.mkdir();
+ File splitApk1 = File.createTempFile("fakeSplitApk1", ".apk", fakeSplitApkApks);
+ mBundletoolJar = File.createTempFile("bundletool", ".jar");
+ File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks);
+ try {
+ mockCleanInstalledApexPackagesAndReboot();
+ when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class)))
+ .thenReturn("serial.json");
+
+ assertTrue(fakeSplitApexApks != null);
+ assertTrue(fakeSplitApkApks != null);
+ assertTrue(mFakeApexApks != null);
+ assertTrue(mFakeApkApks != null);
+ assertEquals(1, fakeSplitApexApks.listFiles().length);
+ assertEquals(2, fakeSplitApkApks.listFiles().length);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApexApks);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApkApks);
+
+ mMockDevice.waitForDeviceAvailable();
+
+ List<String> trainInstallCmd = new ArrayList<>();
+ trainInstallCmd.add("install-multi-package");
+ trainInstallCmd.add(splitApex.getAbsolutePath());
+ String cmd = "";
+ for (File f : fakeSplitApkApks.listFiles()) {
+ if (!cmd.isEmpty()) {
+ cmd += ":" + f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ } else {
+ cmd += f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ }
+ }
+ trainInstallCmd.add(cmd);
+ EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
+ .andReturn("Success")
+ .once();
+ mMockDevice.reboot();
+ Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
+ activatedApex.add(
+ new ApexInfo(
+ SPLIT_APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"));
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(activatedApex).times(3);
+ EasyMock.expect(mMockDevice.uninstallPackage(SPLIT_APK_PACKAGE_NAME))
+ .andReturn(null)
+ .once();
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX_PACKAGE_NAME);
+ installableModules.add(SPLIT_APK_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ Mockito.verify(mMockBundletoolUtil, times(1))
+ .generateDeviceSpecFile(Mockito.any(ITestDevice.class));
+ // Extract splits 1 time to get the package name for the module, and again during
+ // installation.
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ } finally {
+ FileUtil.recursiveDelete(trainFolder);
+ FileUtil.deleteFile(trainFolder);
+ FileUtil.deleteFile(mFakeApexApks);
+ FileUtil.deleteFile(mFakeApkApks);
+ FileUtil.recursiveDelete(fakeSplitApexApks);
+ FileUtil.deleteFile(fakeSplitApexApks);
+ FileUtil.recursiveDelete(fakeSplitApkApks);
+ FileUtil.deleteFile(fakeSplitApkApks);
+ FileUtil.deleteFile(mBundletoolJar);
+ }
+ }
+
+ @Test
+ public void testInstallUsingBundletool_AllFilesHaveAbsolutePath() throws Exception {
+ mFakeApexApks = File.createTempFile("fakeApex", ".apks");
+ mFakeApkApks = File.createTempFile("fakeApk", ".apks");
+ mInstallApexModuleTargetPreparer.addTestFile(mFakeApexApks);
+ mInstallApexModuleTargetPreparer.addTestFile(mFakeApkApks);
+
+ File fakeSplitApexApks = File.createTempFile("ApexSplits", "");
+ fakeSplitApexApks.delete();
+ fakeSplitApexApks.mkdir();
+ File splitApex = File.createTempFile("fakeSplitApex", ".apex", fakeSplitApexApks);
+
+ File fakeSplitApkApks = File.createTempFile("ApkSplits", "");
+ fakeSplitApkApks.delete();
+ fakeSplitApkApks.mkdir();
+ File splitApk1 = File.createTempFile("fakeSplitApk1", ".apk", fakeSplitApkApks);
+ mBundletoolJar = File.createTempFile("/fake/absolute/path/bundletool", ".jar");
+ File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks);
+ try {
+ mockCleanInstalledApexPackagesAndReboot();
+ when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class)))
+ .thenReturn("serial.json");
+
+ assertTrue(fakeSplitApexApks != null);
+ assertTrue(fakeSplitApkApks != null);
+ assertTrue(mFakeApexApks != null);
+ assertTrue(mFakeApkApks != null);
+ assertEquals(1, fakeSplitApexApks.listFiles().length);
+ assertEquals(2, fakeSplitApkApks.listFiles().length);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApexApks);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApkApks);
+
+ mMockDevice.waitForDeviceAvailable();
+
+ List<String> trainInstallCmd = new ArrayList<>();
+ trainInstallCmd.add("install-multi-package");
+ trainInstallCmd.add(splitApex.getAbsolutePath());
+ String cmd = "";
+ for (File f : fakeSplitApkApks.listFiles()) {
+ if (!cmd.isEmpty()) {
+ cmd += ":" + f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ } else {
+ cmd += f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ }
+ }
+ trainInstallCmd.add(cmd);
+ EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
+ .andReturn("Success")
+ .once();
+ mMockDevice.reboot();
+ Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
+ activatedApex.add(
+ new ApexInfo(
+ SPLIT_APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"));
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(activatedApex).times(3);
+ EasyMock.expect(mMockDevice.uninstallPackage(SPLIT_APK_PACKAGE_NAME))
+ .andReturn(null)
+ .once();
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX_PACKAGE_NAME);
+ installableModules.add(SPLIT_APK_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ Mockito.verify(mMockBundletoolUtil, times(1))
+ .generateDeviceSpecFile(Mockito.any(ITestDevice.class));
+ // Extract splits 1 time to get the package name for the module, and again during
+ // installation.
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ } finally {
+ FileUtil.deleteFile(mFakeApexApks);
+ FileUtil.deleteFile(mFakeApkApks);
+ FileUtil.recursiveDelete(fakeSplitApexApks);
+ FileUtil.deleteFile(fakeSplitApexApks);
+ FileUtil.recursiveDelete(fakeSplitApkApks);
+ FileUtil.deleteFile(fakeSplitApkApks);
+ FileUtil.deleteFile(mBundletoolJar);
+ }
+ }
+
+ @Test
public void testInstallUsingBundletool_skipModuleNotPreloaded() throws Exception {
mSetter.setOptionValue("ignore-if-module-not-preloaded", "true");
mInstallApexModuleTargetPreparer.addTestFileName(SPLIT_APEX_APKS_NAME);
@@ -1036,6 +1717,7 @@
.once();
Set<String> installableModules = new HashSet<>();
installableModules.add(SPLIT_APK_PACKAGE_NAME);
+
EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
EasyMock.replay(mMockBuildInfo, mMockDevice);
@@ -1115,6 +1797,22 @@
EasyMock.expectLastCall().once();
}
+ private void mockCleanInstalledApexPackagesAndReboot() throws DeviceNotAvailableException {
+ mMockDevice.deleteFile(APEX_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(2);
+ mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(2);
+ mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(2);
+ CommandResult res = new CommandResult();
+ res.setStdout("test.apex");
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+ }
+
@Test
public void testSetupAndTearDown_noModulesPreloaded() throws Exception {
mSetter.setOptionValue("ignore-if-module-not-preloaded", "true");
@@ -1150,19 +1848,7 @@
mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME);
// Module not preloaded.
mInstallApexModuleTargetPreparer.addTestFileName(APK2_NAME);
- mMockDevice.deleteFile(APEX_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- CommandResult res = new CommandResult();
- res.setStdout("test.apex");
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
- mMockDevice.reboot();
- EasyMock.expectLastCall();
+ mockCleanInstalledApexPackagesAndReboot();
mockSuccessfulInstallMultiPackageAndReboot();
Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
activatedApex.add(
@@ -1192,19 +1878,7 @@
mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME);
mInstallApexModuleTargetPreparer.addTestFileName(APK2_NAME);
- mMockDevice.deleteFile(APEX_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- CommandResult res = new CommandResult();
- res.setStdout("test.apex");
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
- mMockDevice.reboot();
- EasyMock.expectLastCall();
+ mockCleanInstalledApexPackagesAndReboot();
Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
activatedApex.add(
new ApexInfo(
diff --git a/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java b/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
index 16ec66f..853803c 100644
--- a/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
@@ -42,6 +42,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.IOException;
import java.io.File;
import java.util.Set;
@@ -645,6 +646,53 @@
}
}
+ /**
+ * Test that if multiple files exists after delayed partial download, push the one with matching
+ * ABI.
+ */
+ @Test
+ public void testPush_moduleName_files_abi_delayedDownload() throws Exception {
+ mOptionSetter.setOptionValue("push", "file->/data/local/tmp/file");
+ mPreparer.setAbi(new Abi("x86", "32"));
+
+ mPreparer.setInvocationContext(createModuleWithName("aaaaa"));
+ File tmpFolder = FileUtil.createTempDir("push-file-tests-dir");
+ IDeviceBuildInfo info =
+ new DeviceBuildInfo() {
+ @Override
+ public File stageRemoteFile(String fileName, File workingDir) {
+ try {
+ File file_64 =
+ new File(tmpFolder, "target/testcases/aaaaa/x86_64/file");
+ FileUtil.mkdirsRWX(file_64.getParentFile());
+ file_64.createNewFile();
+ File file_32 = new File(tmpFolder, "target/testcases/aaaaa/x86/file");
+ FileUtil.mkdirsRWX(file_32.getParentFile());
+ file_32.createNewFile();
+ // Return the file with mismatched ABI.
+ return file_64;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+ };
+ try {
+ info.setFile(BuildInfoFileKey.TESTDIR_IMAGE, tmpFolder, "v1");
+ EasyMock.expect(
+ mMockDevice.pushFile(
+ EasyMock.eq(
+ new File(tmpFolder, "target/testcases/aaaaa/x86/file")),
+ EasyMock.eq("/data/local/tmp/file")))
+ .andReturn(true);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
+ EasyMock.replay(mMockDevice);
+ mPreparer.setUp(mTestInfo);
+ EasyMock.verify(mMockDevice);
+ } finally {
+ FileUtil.recursiveDelete(tmpFolder);
+ }
+ }
+
@Test
public void testPush_moduleName_ignored() throws Exception {
mOptionSetter.setOptionValue("push", "lib64->/data/local/tmp/lib");
diff --git a/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java b/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java
index 133bad5..20754fc 100644
--- a/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java
@@ -21,7 +21,10 @@
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.replay;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
import com.android.tradefed.build.BuildInfo;
import com.android.tradefed.build.IBuildInfo;
@@ -30,6 +33,8 @@
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.IRunUtil;
+import com.google.common.base.Throwables;
+
import junit.framework.TestCase;
import java.io.File;
@@ -105,4 +110,44 @@
mPreparer.installDeps(buildInfo, mMockDevice);
assertTrue(buildInfo.getFile("PYTHONPATH") == null);
}
+
+ public void testStartVirtualenv_throwTSE_whenVirtualenvNotFound() throws Exception {
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ result.setStdout("bash: virtualenv: command not found");
+ expect(mMockRunUtil.runTimedCmd(anyLong(), eq("virtualenv"), eq("--version")))
+ .andReturn(result);
+ replay(mMockRunUtil);
+
+ try {
+ mPreparer.startVirtualenv(new BuildInfo(), mMockDevice);
+ fail("startVirtualenv succeeded despite a failed command");
+ } catch (TargetSetupError e) {
+ assertThat(
+ String.format(
+ "An unexpected exception was thrown:\n%s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ containsString("virtualenv is not installed."));
+ }
+ }
+
+ public void testStartVirtualenv_throwTSE_whenVirtualenvIsTooOld() throws Exception {
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ result.setStdout("virtualenv 16.7.10 from /path/to/site-packages/virtualenv/__init__.py");
+ expect(mMockRunUtil.runTimedCmd(anyLong(), eq("virtualenv"), eq("--version")))
+ .andReturn(result);
+ replay(mMockRunUtil);
+
+ try {
+ mPreparer.startVirtualenv(new BuildInfo(), mMockDevice);
+ fail("startVirtualenv succeeded despite a failed command");
+ } catch (TargetSetupError e) {
+ assertEquals(
+ String.format(
+ "An unexpected exception was thrown:\n%s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ "virtualenv is too old. Required: >=20.0.1, yours: 16.7.10");
+ }
+ }
}
\ No newline at end of file
diff --git a/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java
index 3f7ae6d..a8f95cf 100644
--- a/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java
@@ -20,24 +20,29 @@
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.device.IDeviceManager;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.IRunUtil;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Answers;
+import org.mockito.InOrder;
import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.io.OutputStream;
import java.util.Arrays;
@@ -45,21 +50,24 @@
import java.util.List;
/** Unit test for {@link RunHostCommandTargetPreparer}. */
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
public final class RunHostCommandTargetPreparerTest {
private static final String DEVICE_SERIAL = "123456";
private static final String FULL_COMMAND = "command \t\t\t \t argument $SERIAL";
- @Mock private ITestDevice mDevice;
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private TestInformation mTestInfo;
+
@Mock private RunHostCommandTargetPreparer.BgCommandLog mBgCommandLog;
@Mock private IRunUtil mRunUtil;
+ @Mock private IDeviceManager mDeviceManager;
private RunHostCommandTargetPreparer mPreparer;
- private TestInformation mTestInfo;
@Before
public void setUp() {
- when(mDevice.getSerialNumber()).thenReturn(DEVICE_SERIAL);
mPreparer =
new RunHostCommandTargetPreparer() {
@Override
@@ -68,13 +76,16 @@
}
@Override
+ IDeviceManager getDeviceManager() {
+ return mDeviceManager;
+ }
+
+ @Override
protected List<BgCommandLog> createBgCommandLogs() {
return Collections.singletonList(mBgCommandLog);
}
};
- IInvocationContext context = new InvocationContext();
- context.addAllocatedDevice("device", mDevice);
- mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build();
+ when(mTestInfo.getDevice().getSerialNumber()).thenReturn(DEVICE_SERIAL);
}
@Test
@@ -89,6 +100,10 @@
// Verify timeout and command (split, removed whitespace, and device serial)
mPreparer.setUp(mTestInfo);
verify(mRunUtil).runTimedCmd(eq(10L), eq("command"), eq("argument"), eq(DEVICE_SERIAL));
+
+ // No flashing permit taken/returned by default
+ verify(mDeviceManager, never()).takeFlashingPermit();
+ verify(mDeviceManager, never()).returnFlashingPermit();
}
@Test
@@ -120,6 +135,24 @@
}
@Test
+ public void testSetUp_flashingPermit() throws Exception {
+ OptionSetter optionSetter = new OptionSetter(mPreparer);
+ optionSetter.setOptionValue("host-setup-command", FULL_COMMAND);
+ optionSetter.setOptionValue("use-flashing-permit", "true");
+
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ when(mRunUtil.runTimedCmd(anyLong(), any())).thenReturn(result);
+
+ // Verify command ran with flashing permit
+ mPreparer.setUp(mTestInfo);
+ InOrder inOrder = inOrder(mRunUtil, mDeviceManager);
+ inOrder.verify(mDeviceManager).takeFlashingPermit();
+ inOrder.verify(mRunUtil)
+ .runTimedCmd(anyLong(), eq("command"), eq("argument"), eq(DEVICE_SERIAL));
+ inOrder.verify(mDeviceManager).returnFlashingPermit();
+ }
+
+ @Test
public void testTearDown() throws Exception {
OptionSetter optionSetter = new OptionSetter(mPreparer);
optionSetter.setOptionValue("host-teardown-command", FULL_COMMAND);
@@ -131,6 +164,10 @@
// Verify timeout and command (split, removed whitespace, and device serial)
mPreparer.tearDown(mTestInfo, null);
verify(mRunUtil).runTimedCmd(eq(10L), eq("command"), eq("argument"), eq(DEVICE_SERIAL));
+
+ // No flashing permit taken/returned by default
+ verify(mDeviceManager, never()).takeFlashingPermit();
+ verify(mDeviceManager, never()).returnFlashingPermit();
}
@Test
@@ -146,6 +183,24 @@
}
@Test
+ public void testTearDown_flashingPermit() throws Exception {
+ OptionSetter optionSetter = new OptionSetter(mPreparer);
+ optionSetter.setOptionValue("host-teardown-command", FULL_COMMAND);
+ optionSetter.setOptionValue("use-flashing-permit", "true");
+
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ when(mRunUtil.runTimedCmd(anyLong(), any())).thenReturn(result);
+
+ // Verify command ran with flashing permit
+ mPreparer.tearDown(mTestInfo, null);
+ InOrder inOrder = inOrder(mRunUtil, mDeviceManager);
+ inOrder.verify(mDeviceManager).takeFlashingPermit();
+ inOrder.verify(mRunUtil)
+ .runTimedCmd(anyLong(), eq("command"), eq("argument"), eq(DEVICE_SERIAL));
+ inOrder.verify(mDeviceManager).returnFlashingPermit();
+ }
+
+ @Test
public void testBgCommand() throws Exception {
OptionSetter optionSetter = new OptionSetter(mPreparer);
optionSetter.setOptionValue("host-background-command", FULL_COMMAND);
diff --git a/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java
index 4125fa1..236498a 100644
--- a/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -42,6 +43,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Answers;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -117,6 +119,9 @@
verify(mRunUtil).runTimedCmd(10L, mScriptFile.getAbsolutePath());
// Verify that script is executable
assertTrue(mScriptFile.canExecute());
+ // No flashing permit taken/returned by default
+ verify(mDeviceManager, never()).takeFlashingPermit();
+ verify(mDeviceManager, never()).returnFlashingPermit();
}
@Test
@@ -155,11 +160,11 @@
@Test
public void testSetUp_pathVariable() throws Exception {
mOptionSetter.setOptionValue("script-file", mScriptFile.getAbsolutePath());
- // Create and set dummy adb binary
+ // Create and set no-op adb binary
Path adbDir = Files.createTempDirectory(mWorkDir.toPath(), "adb");
File adbBinary = File.createTempFile("adb", ".sh", adbDir.toFile());
when(mTestInfo.executionFiles().get(eq(FilesKey.ADB_BINARY))).thenReturn(adbBinary);
- // Create and set dummy fastboot binary
+ // Create and set no-op fastboot binary
Path fastbootDir = Files.createTempDirectory(mWorkDir.toPath(), "fastboot");
File fastbootBinary = File.createTempFile("fastboot", ".sh", fastbootDir.toFile());
when(mDeviceManager.getFastbootPath()).thenReturn(fastbootBinary.getAbsolutePath());
@@ -169,4 +174,16 @@
mPreparer.setUp(mTestInfo);
verify(mRunUtil).setEnvVariable("PATH", expectedPath);
}
+
+ @Test
+ public void testSetUp_flashingPermit() throws Exception {
+ mOptionSetter.setOptionValue("script-file", mScriptFile.getAbsolutePath());
+ mOptionSetter.setOptionValue("use-flashing-permit", "true");
+ // Verify script executed with flashing permit
+ mPreparer.setUp(mTestInfo);
+ InOrder inOrder = inOrder(mRunUtil, mDeviceManager);
+ inOrder.verify(mDeviceManager).takeFlashingPermit();
+ inOrder.verify(mRunUtil).runTimedCmd(anyLong(), eq(mScriptFile.getAbsolutePath()));
+ inOrder.verify(mDeviceManager).returnFlashingPermit();
+ }
}
diff --git a/tests/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java
new file mode 100644
index 0000000..14e324b
--- /dev/null
+++ b/tests/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 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.targetprep;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.UserInfo;
+import com.android.tradefed.invoker.TestInformation;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public class RunOnSecondaryUserTargetPreparerTest {
+
+ private static final String CREATED_USER_2_MESSAGE = "Created user id 2";
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private TestInformation mTestInfo;
+
+ private RunOnSecondaryUserTargetPreparer mPreparer;
+ private OptionSetter mOptionSetter;
+
+ @Before
+ public void setUp() throws Exception {
+ mPreparer = new RunOnSecondaryUserTargetPreparer();
+ mOptionSetter = new OptionSetter(mPreparer);
+ }
+
+ @Test
+ public void setUp_createsAndStartsSecondaryUser() throws Exception {
+ String expectedCreateUserCommand = "pm create-user secondary";
+ String expectedStartUserCommand = "am start-user -w 2";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_2_MESSAGE);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice()).executeShellCommand(expectedCreateUserCommand);
+ verify(mTestInfo.getDevice()).executeShellCommand(expectedStartUserCommand);
+ }
+
+ @Test
+ public void setUp_secondaryUserAlreadyExists_doesNotCreateSecondaryUser() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(2, new UserInfo(2, "secondary", /* flag= */ 0, /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice(), never()).executeShellCommand(any());
+ }
+
+ @Test
+ public void setUp_secondaryUserAlreadyExists_runsTestAsExistingUser() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(3, new UserInfo(3, "secondary", /* flag= */ 0, /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.properties())
+ .put(RunOnWorkProfileTargetPreparer.RUN_TESTS_AS_USER_KEY, "3");
+ }
+
+ @Test
+ public void setUp_setsRunTestsAsUser() throws Exception {
+ String expectedCreateUserCommand = "pm create-user secondary";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_2_MESSAGE);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.properties())
+ .put(RunOnSecondaryUserTargetPreparer.RUN_TESTS_AS_USER_KEY, "2");
+ }
+
+ @Test
+ public void setUp_secondaryUserAlreadyExists_installsPackagesInExistingUser() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(3, new UserInfo(3, "secondary", /* flag= */ 0, /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+ mOptionSetter.setOptionValue(
+ RunOnWorkProfileTargetPreparer.TEST_PACKAGE_NAME_OPTION, "com.android.testpackage");
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice())
+ .executeShellCommand("pm install-existing --user 3 com.android.testpackage");
+ }
+
+ @Test
+ public void setUp_installsPackagesInSecondaryUser() throws Exception {
+ String expectedCreateUserCommand = "pm create-user secondary";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_2_MESSAGE);
+ mOptionSetter.setOptionValue(
+ RunOnSecondaryUserTargetPreparer.TEST_PACKAGE_NAME_OPTION,
+ "com.android.testpackage");
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice())
+ .executeShellCommand("pm install-existing --user 2 com.android.testpackage");
+ }
+
+ @Test
+ public void setUp_workProfileAlreadyExists_disablesTearDown() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(3, new UserInfo(3, "secondary", /* flag= */ 0, /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+ mOptionSetter.setOptionValue("disable-tear-down", "false");
+
+ mPreparer.setUp(mTestInfo);
+
+ assertThat(mPreparer.isTearDownDisabled()).isTrue();
+ }
+
+ @Test
+ public void setUp_doesNotDisableTearDown() throws Exception {
+ String expectedCreateUserCommand = "pm create-user secondary";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_2_MESSAGE);
+ mOptionSetter.setOptionValue("disable-tear-down", "false");
+
+ mPreparer.setUp(mTestInfo);
+
+ assertThat(mPreparer.isTearDownDisabled()).isFalse();
+ }
+
+ @Test
+ public void tearDown_removesSecondaryUser() throws Exception {
+ when(mTestInfo.properties().get(RunOnSecondaryUserTargetPreparer.RUN_TESTS_AS_USER_KEY))
+ .thenReturn("2");
+
+ mPreparer.tearDown(mTestInfo, /* throwable= */ null);
+
+ verify(mTestInfo.getDevice()).removeUser(2);
+ }
+}
diff --git a/tests/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java
new file mode 100644
index 0000000..5845cd2
--- /dev/null
+++ b/tests/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2020 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.targetprep;
+
+import static com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer.RUN_TESTS_AS_USER_KEY;
+import static com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer.TEST_PACKAGE_NAME_OPTION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.UserInfo;
+import com.android.tradefed.invoker.TestInformation;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public class RunOnWorkProfileTargetPreparerTest {
+ private static final String CREATED_USER_10_MESSAGE = "Created user id 10";
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private TestInformation mTestInfo;
+
+ private RunOnWorkProfileTargetPreparer mPreparer;
+ private OptionSetter mOptionSetter;
+
+ @Before
+ public void setUp() throws Exception {
+ mPreparer = new RunOnWorkProfileTargetPreparer();
+ mOptionSetter = new OptionSetter(mPreparer);
+ }
+
+ @Test
+ public void setUp_createsAndStartsWorkProfile() throws Exception {
+ String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work";
+ String expectedStartUserCommand = "am start-user -w 10";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_10_MESSAGE);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice()).executeShellCommand(expectedCreateUserCommand);
+ verify(mTestInfo.getDevice()).executeShellCommand(expectedStartUserCommand);
+ }
+
+ @Test
+ public void setUp_workProfileAlreadyExists_doesNotCreateWorkProfile() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(
+ 10,
+ new UserInfo(
+ 10,
+ "work",
+ /* flag= */ UserInfo.FLAG_MANAGED_PROFILE,
+ /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice(), never()).executeShellCommand(any());
+ }
+
+ @Test
+ public void setUp_nonZeroCurrentUser_createsWorkProfileForCorrectUser() throws Exception {
+ when(mTestInfo.getDevice().getCurrentUser()).thenReturn(1);
+ String expectedCreateUserCommand = "pm create-user --profileOf 1 --managed work";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_10_MESSAGE);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice()).executeShellCommand(expectedCreateUserCommand);
+ }
+
+ @Test
+ public void setUp_workProfileAlreadyExists_runsTestAsExistingUser() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(
+ 11,
+ new UserInfo(
+ 11,
+ "work",
+ /* flag= */ UserInfo.FLAG_MANAGED_PROFILE,
+ /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.properties()).put(RUN_TESTS_AS_USER_KEY, "11");
+ }
+
+ @Test
+ public void setUp_setsRunTestsAsUser() throws Exception {
+ String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_10_MESSAGE);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.properties()).put(RUN_TESTS_AS_USER_KEY, "10");
+ }
+
+ @Test
+ public void setUp_workProfileAlreadyExists_installsPackagesInExistingUser() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(
+ 11,
+ new UserInfo(
+ 11,
+ "work",
+ /* flag= */ UserInfo.FLAG_MANAGED_PROFILE,
+ /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+ mOptionSetter.setOptionValue(TEST_PACKAGE_NAME_OPTION, "com.android.testpackage");
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice())
+ .executeShellCommand("pm install-existing --user 11 com.android.testpackage");
+ }
+
+ @Test
+ public void setUp_installsPackagesInWorkUser() throws Exception {
+ String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_10_MESSAGE);
+ mOptionSetter.setOptionValue(TEST_PACKAGE_NAME_OPTION, "com.android.testpackage");
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice())
+ .executeShellCommand("pm install-existing --user 10 com.android.testpackage");
+ }
+
+ @Test
+ public void setUp_workProfileAlreadyExists_disablesTearDown() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(
+ 11,
+ new UserInfo(
+ 11,
+ "work",
+ /* flag= */ UserInfo.FLAG_MANAGED_PROFILE,
+ /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+ mOptionSetter.setOptionValue("disable-tear-down", "false");
+
+ mPreparer.setUp(mTestInfo);
+
+ assertThat(mPreparer.isTearDownDisabled()).isTrue();
+ }
+
+ @Test
+ public void setUp_doesNotDisableTearDown() throws Exception {
+ String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_10_MESSAGE);
+ mOptionSetter.setOptionValue("disable-tear-down", "false");
+
+ mPreparer.setUp(mTestInfo);
+
+ assertThat(mPreparer.isTearDownDisabled()).isFalse();
+ }
+
+ @Test
+ public void tearDown_removesWorkUser() throws Exception {
+ when(mTestInfo.properties().get(RUN_TESTS_AS_USER_KEY)).thenReturn("10");
+
+ mPreparer.tearDown(mTestInfo, /* throwable= */ null);
+
+ verify(mTestInfo.getDevice()).removeUser(10);
+ }
+}
diff --git a/tests/src/com/android/tradefed/targetprep/multi/MixImageZipPreparerTest.java b/tests/src/com/android/tradefed/targetprep/multi/MixImageZipPreparerTest.java
index e34630a..b0dc5ed 100644
--- a/tests/src/com/android/tradefed/targetprep/multi/MixImageZipPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/multi/MixImageZipPreparerTest.java
@@ -79,7 +79,7 @@
// The strings written to temporary image files.
private static final String DEVICE_CONTENT = "device content";
private static final String SYSTEM_CONTENT = "system content";
- private static final String DUMMY_CONTENT = "\0";
+ private static final String STUB_CONTENT = "\0";
private static final String RESOURCE_CONTENT = "resource content";
private IInvocationContext mMockContext;
@@ -165,8 +165,8 @@
for (String imageName : imageNames) {
mPreparer.addSystemFileName(imageName);
}
- mPreparer.addDummyFileName(PRODUCT_IMAGE_NAME);
- mPreparer.addDummyFileName("missing_dummy.img");
+ mPreparer.addStubFileName(PRODUCT_IMAGE_NAME);
+ mPreparer.addStubFileName("missing_stub.img");
}
private void setUpPreparerAndSystem() throws IOException {
@@ -337,7 +337,7 @@
} else {
// setUpDevice was called.
verifyImage(SYSTEM_CONTENT, mixedImageDir, SYSTEM_IMAGE_NAME);
- verifyImage(DUMMY_CONTENT, mixedImageDir, PRODUCT_IMAGE_NAME);
+ verifyImage(STUB_CONTENT, mixedImageDir, PRODUCT_IMAGE_NAME);
}
if (mResourceBuild != null) {
diff --git a/tests/src/com/android/tradefed/testtype/ArtGTestTest.java b/tests/src/com/android/tradefed/testtype/ArtGTestTest.java
new file mode 100644
index 0000000..a30343e
--- /dev/null
+++ b/tests/src/com/android/tradefed/testtype/ArtGTestTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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;
+
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.MockFileUtil;
+import com.android.tradefed.targetprep.ArtChrootPreparer;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.result.ITestInvocationListener;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ArtGTest}. */
+@RunWith(JUnit4.class)
+public class ArtGTestTest {
+ private ITestInvocationListener mMockInvocationListener = null;
+ private IShellOutputReceiver mMockReceiver = null;
+ private ITestDevice mMockITestDevice = null;
+ private GTest mGTest;
+ private TestInformation mTestInfo;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockInvocationListener = EasyMock.createMock(ITestInvocationListener.class);
+ mMockReceiver = EasyMock.createMock(IShellOutputReceiver.class);
+ mMockITestDevice = EasyMock.createMock(ITestDevice.class);
+ mGTest =
+ new ArtGTest() {
+ @Override
+ IShellOutputReceiver createResultParser(
+ String runName, ITestInvocationListener listener) {
+ return mMockReceiver;
+ }
+ };
+ mGTest.setDevice(mMockITestDevice);
+ mGTest.setNativeTestDevicePath(ArtChrootPreparer.CHROOT_PATH);
+ mTestInfo = TestInformation.newBuilder().build();
+
+ EasyMock.expect(mMockITestDevice.getSerialNumber()).andStubReturn("serial");
+ }
+
+ private void replayMocks() {
+ EasyMock.replay(mMockInvocationListener, mMockITestDevice);
+ }
+
+ private void verifyMocks() {
+ EasyMock.verify(mMockInvocationListener, mMockITestDevice);
+ }
+
+ @Test
+ public void testChroot_testRun() throws DeviceNotAvailableException {
+ final String nativeTestPath = ArtChrootPreparer.CHROOT_PATH;
+ final String test1 = "test1";
+ final String testPath1 = String.format("%s/%s", nativeTestPath, test1);
+
+ MockFileUtil.setMockDirContents(mMockITestDevice, nativeTestPath, test1);
+ EasyMock.expect(mMockITestDevice.doesFileExist(nativeTestPath)).andReturn(true);
+ EasyMock.expect(mMockITestDevice.isDirectory(nativeTestPath)).andReturn(true);
+ EasyMock.expect(mMockITestDevice.isDirectory(testPath1)).andReturn(false);
+ EasyMock.expect(mMockITestDevice.isExecutable(testPath1)).andReturn(true);
+
+ String[] files = new String[] {"test1"};
+ EasyMock.expect(mMockITestDevice.getChildren(nativeTestPath)).andReturn(files);
+ mMockITestDevice.executeShellCommand(
+ EasyMock.startsWith("chroot /data/local/tmp/art-test-chroot /test1"),
+ EasyMock.anyObject(),
+ EasyMock.anyLong(),
+ EasyMock.anyObject(),
+ EasyMock.anyInt());
+
+ replayMocks();
+ mGTest.run(mTestInfo, mMockInvocationListener);
+ verifyMocks();
+ }
+}
diff --git a/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java b/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java
index 23d8b75..9b360e4 100644
--- a/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java
@@ -25,7 +25,6 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestDescription;
@@ -49,9 +48,6 @@
@RunWith(JUnit4.class)
public class ArtRunTestTest {
- // Default run-test name.
- private static final String RUN_TEST_NAME = "run-test";
-
private ITestInvocationListener mMockInvocationListener;
private IAbi mMockAbi;
private ITestDevice mMockITestDevice;
@@ -60,9 +56,9 @@
private ArtRunTest mArtRunTest;
private OptionSetter mSetter;
private TestInformation mTestInfo;
- // Target tests directory.
- private File mTmpTargetTestsDir;
- // Expected output file (under the target tests directory).
+ // Test dependencies directory on host.
+ private File mTmpDepsDir;
+ // Expected output file (within the dependencies directory).
private File mTmpExpectedFile;
@Before
@@ -82,23 +78,22 @@
mArtRunTest.setDevice(mMockITestDevice);
mSetter = new OptionSetter(mArtRunTest);
- // Set up target tests directory and expected output file.
- mTmpTargetTestsDir = FileUtil.createTempDir("target_testcases");
- File runTestDir = new File(mTmpTargetTestsDir, RUN_TEST_NAME);
- runTestDir.mkdir();
- mTmpExpectedFile = new File(runTestDir, "expected.txt");
- FileWriter fw = new FileWriter(mTmpExpectedFile);
- fw.write("output\n");
- fw.close();
-
- // Set the target tests directory in test information object.
- mTestInfo = TestInformation.newBuilder().build();
- mTestInfo.executionFiles().put(FilesKey.TARGET_TESTS_DIRECTORY, mTmpTargetTestsDir);
+ // Temporary test directory (e.g. for the expected-output file).
+ mTmpDepsDir = FileUtil.createTempDir("art-run-test-deps");
+ mTestInfo = TestInformation.newBuilder().setDependenciesFolder(mTmpDepsDir).build();
}
@After
public void tearDown() {
- FileUtil.recursiveDelete(mTmpTargetTestsDir);
+ FileUtil.recursiveDelete(mTmpDepsDir);
+ }
+
+ /** Helper creating an expected-output file within the (temporary) test directory. */
+ private void createExpectedOutputFile(String runTestName) throws IOException {
+ mTmpExpectedFile = new File(mTmpDepsDir, runTestName + "-expected.txt");
+ FileWriter fw = new FileWriter(mTmpExpectedFile);
+ fw.write("output\n");
+ fw.close();
}
/** Helper mocking writing the output of a test command. */
@@ -151,7 +146,9 @@
@Test
public void testRunSingleTest_unsetClasspathOption()
throws ConfigurationException, DeviceNotAvailableException, IOException {
- mSetter.setOptionValue("run-test-name", RUN_TEST_NAME);
+ final String runTestName = "test";
+ mSetter.setOptionValue("run-test-name", runTestName);
+ createExpectedOutputFile(runTestName);
replayMocks();
try {
@@ -163,21 +160,20 @@
verifyMocks();
}
- /** Test the run method for a (single) test. */
- @Test
- public void testRunSingleTest()
+ /** Helper containing testing logic for a (single) test expected to run (and succeed). */
+ private void doTestRunSingleTest(final String runTestName, final String classpath)
throws ConfigurationException, DeviceNotAvailableException, IOException {
- mSetter.setOptionValue("run-test-name", RUN_TEST_NAME);
- final String classpath = "/data/local/tmp/test/test.jar";
+ mSetter.setOptionValue("run-test-name", runTestName);
+ createExpectedOutputFile(runTestName);
mSetter.setOptionValue("classpath", classpath);
// Pre-test checks.
- EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn("");
EasyMock.expect(mMockAbi.getName()).andReturn("abi");
+ EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn("");
String runName = "ArtRunTest_abi";
// Beginning of test.
mMockInvocationListener.testRunStarted(runName, 1);
- TestDescription testId = new TestDescription(runName, RUN_TEST_NAME);
+ TestDescription testId = new TestDescription(runName, runTestName);
mMockInvocationListener.testStarted(testId);
String cmd = String.format("dalvikvm64 -classpath %s Main", classpath);
// Test execution.
@@ -198,6 +194,31 @@
verifyMocks();
}
+ /** Helper containing testing logic for a (single) test expected not to run. */
+ private void doTestDoNotRunSingleTest(final String runTestName, final String classpath)
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ mSetter.setOptionValue("run-test-name", runTestName);
+ createExpectedOutputFile(runTestName);
+ mSetter.setOptionValue("classpath", classpath);
+
+ EasyMock.expect(mMockAbi.getName()).andReturn("abi");
+ replayMocks();
+
+ mArtRunTest.run(mTestInfo, mMockInvocationListener);
+
+ verifyMocks();
+ }
+
+ /** Test the run method for a (single) test. */
+ @Test
+ public void testRunSingleTest()
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ final String runTestName = "test";
+ final String classpath = "/data/local/tmp/test/test.jar";
+
+ doTestRunSingleTest(runTestName, classpath);
+ }
+
/**
* Test the behavior of the run method when the output produced by the shell command on device
* differs from the expected output.
@@ -205,17 +226,19 @@
@Test
public void testRunSingleTest_unexpectedOutput()
throws ConfigurationException, DeviceNotAvailableException, IOException {
- mSetter.setOptionValue("run-test-name", RUN_TEST_NAME);
+ final String runTestName = "test";
+ mSetter.setOptionValue("run-test-name", runTestName);
+ createExpectedOutputFile(runTestName);
final String classpath = "/data/local/tmp/test/test.jar";
mSetter.setOptionValue("classpath", classpath);
// Pre-test checks.
- EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn("");
EasyMock.expect(mMockAbi.getName()).andReturn("abi");
+ EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn("");
String runName = "ArtRunTest_abi";
// Beginning of test.
mMockInvocationListener.testRunStarted(runName, 1);
- TestDescription testId = new TestDescription(runName, RUN_TEST_NAME);
+ TestDescription testId = new TestDescription(runName, runTestName);
mMockInvocationListener.testStarted(testId);
String cmd = String.format("dalvikvm64 -classpath %s Main", classpath);
// Test execution.
@@ -236,4 +259,44 @@
verifyMocks();
}
+
+ /** Test the run method for a (single) test contained in an include filter. */
+ @Test
+ public void testIncludeFilter()
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ final String runTestName = "test";
+ final String classpath = "/data/local/tmp/test/test.jar";
+ // Add an include filter containing the test's name.
+ mArtRunTest.addIncludeFilter(runTestName);
+
+ doTestRunSingleTest(runTestName, classpath);
+ }
+
+ /** Test the run method for a (single) test contained in an exclude filter. */
+ @Test
+ public void testExcludeFilter()
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ final String runTestName = "test";
+ final String classpath = "/data/local/tmp/test/test.jar";
+ // Add an exclude filter containing the test's name.
+ mArtRunTest.addExcludeFilter(runTestName);
+
+ doTestDoNotRunSingleTest(runTestName, classpath);
+ }
+
+ /**
+ * Test the run method for a (single) test contained both in an include and an exclude filter.
+ */
+ @Test
+ public void testIncludeAndExcludeFilter()
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ final String runTestName = "test";
+ final String classpath = "/data/local/tmp/test/test.jar";
+ // Add an include filter containing the test's name.
+ mArtRunTest.addIncludeFilter(runTestName);
+ // Add an exclude filter containing the test's name.
+ mArtRunTest.addExcludeFilter(runTestName);
+
+ doTestDoNotRunSingleTest(runTestName, classpath);
+ }
}
diff --git a/tests/src/com/android/tradefed/testtype/GTestTest.java b/tests/src/com/android/tradefed/testtype/GTestTest.java
index b4dbe2d..7f05104 100644
--- a/tests/src/com/android/tradefed/testtype/GTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/GTestTest.java
@@ -28,10 +28,13 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.MockFileUtil;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.testtype.coverage.CoverageOptions;
+import com.google.common.collect.ImmutableList;
+
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
@@ -39,8 +42,6 @@
import org.junit.runners.JUnit4;
import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -48,6 +49,7 @@
@RunWith(JUnit4.class)
public class GTestTest {
private static final String GTEST_FLAG_FILTER = "--gtest_filter";
+ private IInvocationContext mMockContext = null;
private ITestInvocationListener mMockInvocationListener = null;
private IShellOutputReceiver mMockReceiver = null;
private ITestDevice mMockITestDevice = null;
@@ -62,12 +64,15 @@
/** Helper to initialize the various EasyMocks we'll need. */
@Before
public void setUp() throws Exception {
+ mMockContext = EasyMock.createMock(IInvocationContext.class);
mMockInvocationListener = EasyMock.createMock(ITestInvocationListener.class);
mMockReceiver = EasyMock.createMock(IShellOutputReceiver.class);
mMockITestDevice = EasyMock.createMock(ITestDevice.class);
mMockReceiver.flush();
EasyMock.expectLastCall().anyTimes();
EasyMock.expect(mMockITestDevice.getSerialNumber()).andStubReturn("serial");
+ EasyMock.expect(mMockContext.getDevices())
+ .andStubReturn(ImmutableList.of(mMockITestDevice));
mGTest =
new GTest() {
@Override
@@ -98,21 +103,21 @@
mConfiguration.setCoverageOptions(mCoverageOptions);
mGTest.setConfiguration(mConfiguration);
- mTestInfo = TestInformation.newBuilder().build();
+ mTestInfo = TestInformation.newBuilder().setInvocationContext(mMockContext).build();
}
/**
* Helper that replays all mocks.
*/
private void replayMocks() {
- EasyMock.replay(mMockInvocationListener, mMockITestDevice, mMockReceiver);
+ EasyMock.replay(mMockContext, mMockInvocationListener, mMockITestDevice, mMockReceiver);
}
/**
* Helper that verifies all mocks.
*/
private void verifyMocks() {
- EasyMock.verify(mMockInvocationListener, mMockITestDevice, mMockReceiver);
+ EasyMock.verify(mMockContext, mMockInvocationListener, mMockITestDevice, mMockReceiver);
}
/** Test run when the test dir is not found on the device. */
@@ -489,124 +494,6 @@
verifyMocks();
}
- /** Test cross-process coverage dump for all native processes */
- @Test
- public void testNativeCoverageAllProcesses() throws Exception {
- mCoverageOptionsSetter.setOptionValue("coverage", "true");
- mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "GCOV");
- mCoverageOptionsSetter.setOptionValue("coverage-flush", "true");
-
- final String nativeTestPath = GTest.DEFAULT_NATIVETEST_PATH;
- final String test1 = "test1";
- final String test2 = "test2";
- final String testPath1 = String.format("%s/%s", nativeTestPath, test1);
- final String testPath2 = String.format("%s/%s", nativeTestPath, test2);
-
- MockFileUtil.setMockDirContents(mMockITestDevice, nativeTestPath, test1, test2);
- EasyMock.expect(mMockITestDevice.enableAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.executeShellCommand("mkdir /data/misc/trace/testcoverage"))
- .andReturn("");
- EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 -1")).andReturn("");
- // Wait up to 5 minutes for the device to be available after flushing coverage data.
- mMockITestDevice.waitForDeviceAvailable(5 * 60 * 1000);
- EasyMock.expect(mMockITestDevice.executeShellCommand("rm -rf /data/misc/trace/*"))
- .andReturn("");
- EasyMock.expect(mMockITestDevice.doesFileExist(nativeTestPath)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(nativeTestPath)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(testPath1)).andReturn(false);
- // report the file as executable
- EasyMock.expect(mMockITestDevice.isExecutable(testPath1)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(testPath2)).andReturn(false);
- // report the file as executable
- EasyMock.expect(mMockITestDevice.isExecutable(testPath2)).andReturn(true);
-
- String[] files = new String[] {"test1", "test2"};
- EasyMock.expect(mMockITestDevice.getChildren(nativeTestPath)).andReturn(files);
- mMockITestDevice.executeShellCommand(
- EasyMock.contains(test1),
- EasyMock.same(mMockReceiver),
- EasyMock.anyLong(),
- (TimeUnit) EasyMock.anyObject(),
- EasyMock.anyInt());
- mMockITestDevice.executeShellCommand(
- EasyMock.contains(test2),
- EasyMock.same(mMockReceiver),
- EasyMock.anyLong(),
- (TimeUnit) EasyMock.anyObject(),
- EasyMock.anyInt());
-
- replayMocks();
-
- mGTest.run(mTestInfo, mMockInvocationListener);
- verifyMocks();
- }
-
- /** Test cross-process coverage dump for specific processes */
- @Test
- public void testNativeCoverageSpecificProcesses() throws Exception {
- final List<String> processNames = new ArrayList<>();
- processNames.add("init");
- processNames.add("surfaceflinger");
-
- mCoverageOptionsSetter.setOptionValue("coverage", "true");
- mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "GCOV");
- mCoverageOptionsSetter.setOptionValue("coverage-flush", "true");
- for (String processName : processNames) {
- mCoverageOptionsSetter.setOptionValue("coverage-processes", processName);
- }
-
- final String nativeTestPath = GTest.DEFAULT_NATIVETEST_PATH;
- final String test1 = "test1";
- final String test2 = "test2";
- final String testPath1 = String.format("%s/%s", nativeTestPath, test1);
- final String testPath2 = String.format("%s/%s", nativeTestPath, test2);
-
- MockFileUtil.setMockDirContents(mMockITestDevice, nativeTestPath, test1, test2);
- EasyMock.expect(mMockITestDevice.enableAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.executeShellCommand("mkdir /data/misc/trace/testcoverage"))
- .andReturn("");
- // Get the pids to flush coverage data.
- EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.getProcessPid(processNames.get(0))).andReturn("1");
- EasyMock.expect(mMockITestDevice.getProcessPid(processNames.get(1))).andReturn("1000");
- EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 1 1000")).andReturn("");
- // Wait up to 5 minutes for the device to be available after flushing coverage data.
- mMockITestDevice.waitForDeviceAvailable(5 * 60 * 1000);
-
- // Clear the coverage data.
- EasyMock.expect(mMockITestDevice.executeShellCommand("rm -rf /data/misc/trace/*"))
- .andReturn("");
- EasyMock.expect(mMockITestDevice.doesFileExist(nativeTestPath)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(nativeTestPath)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(testPath1)).andReturn(false);
- // report the file as executable
- EasyMock.expect(mMockITestDevice.isExecutable(testPath1)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(testPath2)).andReturn(false);
- // report the file as executable
- EasyMock.expect(mMockITestDevice.isExecutable(testPath2)).andReturn(true);
-
- String[] files = new String[] {"test1", "test2"};
- EasyMock.expect(mMockITestDevice.getChildren(nativeTestPath)).andReturn(files);
- mMockITestDevice.executeShellCommand(
- EasyMock.contains(test1),
- EasyMock.same(mMockReceiver),
- EasyMock.anyLong(),
- (TimeUnit) EasyMock.anyObject(),
- EasyMock.anyInt());
- mMockITestDevice.executeShellCommand(
- EasyMock.contains(test2),
- EasyMock.same(mMockReceiver),
- EasyMock.anyLong(),
- (TimeUnit) EasyMock.anyObject(),
- EasyMock.anyInt());
-
- replayMocks();
-
- mGTest.run(mTestInfo, mMockInvocationListener);
- verifyMocks();
- }
-
@Test
public void testGetFileName() {
String expected = "bar";
diff --git a/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java b/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
index 51877ce..8ce4d0a 100644
--- a/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
@@ -23,6 +23,7 @@
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.util.StringEscapeUtils;
import junit.framework.TestCase;
@@ -348,8 +349,7 @@
String filterFlag = mGoogleBenchmarkTest.getFilterFlagForFilters(filters);
assertEquals(
String.format(
- " %s=%s",
- GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "filter1\\|filter2"),
+ " %s=%s", GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "filter1|filter2"),
filterFlag);
}
@@ -366,8 +366,7 @@
String filterFlag = mGoogleBenchmarkTest.getFilterFlagForTests(tests);
assertEquals(
String.format(
- " %s=%s",
- GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "^test1$\\|^test2$"),
+ " %s=%s", GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "^test1$|^test2$"),
filterFlag);
}
@@ -407,7 +406,10 @@
String incFilterFlag =
mGoogleBenchmarkTest.getFilterFlagForFilters(
mGoogleBenchmarkTest.getIncludeFilters());
- EasyMock.expect(mMockITestDevice.executeShellCommand(EasyMock.contains(incFilterFlag)))
+ EasyMock.expect(
+ mMockITestDevice.executeShellCommand(
+ EasyMock.contains(
+ StringEscapeUtils.escapeShell(incFilterFlag))))
.andReturn(incTests);
} else {
EasyMock.expect(
@@ -422,14 +424,17 @@
String excFilterFlag =
mGoogleBenchmarkTest.getFilterFlagForFilters(
mGoogleBenchmarkTest.getExcludeFilters());
- EasyMock.expect(mMockITestDevice.executeShellCommand(EasyMock.contains(excFilterFlag)))
+ EasyMock.expect(
+ mMockITestDevice.executeShellCommand(
+ EasyMock.contains(
+ StringEscapeUtils.escapeShell(excFilterFlag))))
.andReturn(excTests);
}
if (filteredTests != null && filteredTests.size() > 0) {
// Runningt filtered tests
String testFilterFlag = mGoogleBenchmarkTest.getFilterFlagForTests(filteredTests);
mMockITestDevice.executeShellCommand(
- EasyMock.contains(testFilterFlag),
+ EasyMock.contains(StringEscapeUtils.escapeShell(testFilterFlag)),
EasyMock.same(mMockReceiver),
EasyMock.anyLong(),
(TimeUnit) EasyMock.anyObject(),
diff --git a/tests/src/com/android/tradefed/testtype/HostTestTest.java b/tests/src/com/android/tradefed/testtype/HostTestTest.java
index 570d0fd..3f03776 100644
--- a/tests/src/com/android/tradefed/testtype/HostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/HostTestTest.java
@@ -37,6 +37,8 @@
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
@@ -346,7 +348,8 @@
@After
public void tearDown() throws Exception {
- throw new DeviceNotAvailableException("dnae", "serial");
+ throw new DeviceNotAvailableException(
+ "dnae", "serial", DeviceErrorIdentifier.DEVICE_UNAVAILABLE);
}
}
@@ -1323,7 +1326,7 @@
EasyMock.eq(test1),
EasyMock.contains("MultipleFailureException, There were 2 errors:"));
mListener.testEnded(EasyMock.eq(test1), (HashMap<String, Metric>) EasyMock.anyObject());
- Capture<String> captureRunFailure = new Capture<>();
+ Capture<FailureDescription> captureRunFailure = new Capture<>();
mListener.testRunFailed(EasyMock.capture(captureRunFailure));
mListener.testRunEnded(EasyMock.anyLong(), (HashMap<String, Metric>) EasyMock.anyObject());
EasyMock.replay(mListener);
@@ -1334,10 +1337,12 @@
// Expected
}
EasyMock.verify(mListener);
- String failure = captureRunFailure.getValue();
+ FailureDescription failure = captureRunFailure.getValue();
assertTrue(
- failure.startsWith(
- "Failed with trace: com.android.tradefed.device.DeviceNotAvailableException: dnae"));
+ failure.getErrorMessage()
+ .startsWith(
+ "com.android.tradefed.device.DeviceNotAvailableException: dnae"));
+ assertEquals(FailureStatus.LOST_SYSTEM_UNDER_TEST, failure.getFailureStatus());
}
/**
@@ -2178,7 +2183,8 @@
setter.setOptionValue("class", Junit4TestClass.class.getName());
// First class fail with the run failure
mListener.testRunStarted(EasyMock.anyObject(), EasyMock.eq(1));
- mListener.testRunFailed(EasyMock.contains("Failed with trace:"));
+ Capture<FailureDescription> capture = new Capture<>();
+ mListener.testRunFailed(EasyMock.capture(capture));
mListener.testRunEnded(EasyMock.anyLong(), (HashMap<String, Metric>) EasyMock.anyObject());
// Second class run properly
@@ -2195,6 +2201,8 @@
assertEquals(3, mHostTest.countTestCases());
mHostTest.run(mTestInfo, mListener);
EasyMock.verify(mListener);
+ FailureDescription failure = capture.getValue();
+ assertEquals("Exception with no error message", failure.getErrorMessage());
}
/** JUnit4 class that throws within its @Before */
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
index def0130..01d7a60 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
@@ -16,6 +16,8 @@
package com.android.tradefed.testtype;
+import static com.android.tradefed.testtype.InstrumentationTest.RUN_TESTS_AS_USER_KEY;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -24,10 +26,9 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollectionOf;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -54,7 +55,6 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.ITestLifeCycleReceiver;
import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.testtype.coverage.CoverageOptions;
@@ -106,6 +106,7 @@
private TestInformation mTestInfo = null;
private CoverageOptions mCoverageOptions = null;
private OptionSetter mCoverageOptionsSetter = null;
+ private IInvocationContext mContext = null;
// The mock objects.
@Mock IDevice mMockIDevice;
@@ -160,8 +161,9 @@
mConfig.setCoverageOptions(mCoverageOptions);
mInstrumentationTest.setConfiguration(mConfig);
- IInvocationContext context = new InvocationContext();
- mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build();
+ mContext = new InvocationContext();
+ mContext.addAllocatedDevice("main", mMockTestDevice);
+ mTestInfo = TestInformation.newBuilder().setInvocationContext(mContext).build();
}
/** Test normal run scenario. */
@@ -202,6 +204,27 @@
}
@Test
+ public void testRun_nullTestInfo() throws Exception {
+ mInstrumentationTest.run(/* testInfo= */ null, mMockListener);
+
+ verify(mMockTestDevice, atLeastOnce())
+ .runInstrumentationTests(
+ any(IRemoteAndroidTestRunner.class), any(ITestInvocationListener.class));
+ }
+
+ @Test
+ public void testRun_runTestsAsUser() throws DeviceNotAvailableException {
+ mTestInfo.properties().put(RUN_TESTS_AS_USER_KEY, "10");
+ mInstrumentationTest.run(mTestInfo, mMockListener);
+
+ verify(mMockTestDevice, atLeastOnce())
+ .runInstrumentationTestsAsUser(
+ any(IRemoteAndroidTestRunner.class),
+ eq(10),
+ any(ITestInvocationListener.class));
+ }
+
+ @Test
public void testRun_bothAbi() throws DeviceNotAvailableException {
mInstrumentationTest.setAbi(mock(IAbi.class));
mInstrumentationTest.setForceAbi("test");
@@ -719,51 +742,6 @@
inOrder.verifyNoMoreInteractions();
}
- /** Verify that all tests are re-run when there is a failure during a coverage run. */
- @Test
- public void testRun_mergedCoverage()
- throws ConfigurationException, DeviceNotAvailableException {
- mInstrumentationTest.setRerunMode(true);
- mInstrumentationTest.setMergeCoverageMeasurements(true);
- mCoverageOptionsSetter.setOptionValue("coverage", "true");
- mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "JACOCO");
-
- // Mock collected tests
- RunInstrumentationTestsAnswer runTests =
- (runner, listener) -> {
- // perform call back on listener to show run of two tests
- listener.testRunStarted(TEST_PACKAGE_VALUE, 2);
- listener.testStarted(TEST1);
- listener.testEnded(TEST1, EMPTY_STRING_MAP);
- listener.testStarted(TEST2);
- listener.testEnded(TEST2, EMPTY_STRING_MAP);
- listener.testRunEnded(1, EMPTY_STRING_MAP);
- return true;
- };
-
- doAnswer(runTests)
- .when(mMockTestDevice)
- .runInstrumentationTests(
- any(IRemoteAndroidTestRunner.class), any(ITestLifeCycleReceiver.class));
- doReturn(true).when(mMockTestDevice).enableAdbRoot();
- doReturn("").when(mMockTestDevice).executeShellCommand(anyString());
-
- mInstrumentationTest.run(mTestInfo, mMockListener);
-
- InOrder inOrder = Mockito.inOrder(mMockListener);
- inOrder.verify(mMockListener).testRunStarted(TEST_PACKAGE_VALUE, 2);
- inOrder.verify(mMockListener).testStarted(eq(TEST1), anyLong());
- inOrder.verify(mMockListener).testEnded(eq(TEST1), anyLong(), eq(EMPTY_STRING_MAP));
- inOrder.verify(mMockListener).testStarted(eq(TEST2), anyLong());
- inOrder.verify(mMockListener).testEnded(eq(TEST2), anyLong(), eq(EMPTY_STRING_MAP));
- inOrder.verify(mMockListener).testRunEnded(1, EMPTY_STRING_MAP);
- inOrder.verify(mMockListener).testRunStarted(eq("mergeCoverageMeasurements"), anyInt());
- inOrder.verify(mMockListener)
- .testLog(eq("merged_runtime_coverage"), eq(LogDataType.COVERAGE), any());
- inOrder.verify(mMockListener).testRunEnded(anyLong(), eq(EMPTY_STRING_MAP));
- inOrder.verifyNoMoreInteractions();
- }
-
/** Test the reboot before re-run option. */
@Test
public void testRun_rebootBeforeReRun() throws DeviceNotAvailableException {
@@ -1074,8 +1052,9 @@
inOrder.verify(mMockListener).testRunStarted("fakeName", 1);
TestDescription tid = new TestDescription("fakeclass", "fakemethod0");
inOrder.verify(mMockListener).testStarted(tid, 0L);
- inOrder.verify(mMockListener)
- .testFailed(tid, "Instrumentation run failed due to 'Process crashed.'");
+ FailureDescription failure =
+ FailureDescription.create("Instrumentation run failed due to 'Process crashed.'");
+ inOrder.verify(mMockListener).testFailed(tid, failure);
inOrder.verify(mMockListener).testEnded(tid, 15L, EMPTY_STRING_MAP);
inOrder.verify(mMockListener)
.testRunFailed(
@@ -1085,32 +1064,6 @@
inOrder.verify(mMockListener).testRunEnded(1, EMPTY_STRING_MAP);
}
- @Test
- public void testAddCoverageListener_enabled() throws ConfigurationException {
- mCoverageOptionsSetter.setOptionValue("coverage", "true");
- mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "GCOV");
- mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "JACOCO");
-
- ITestInvocationListener listener =
- mInstrumentationTest.addJavaCoverageListenerIfEnabled(mMockListener);
- assertThat(listener).isInstanceOf(JavaCodeCoverageListener.class);
-
- listener = mInstrumentationTest.addGcovCoverageListenerIfEnabled(mMockListener);
- assertThat(listener).isInstanceOf(NativeCodeCoverageListener.class);
- }
-
- @Test
- public void testAddCoverageListener_disabled() throws ConfigurationException {
- mCoverageOptionsSetter.setOptionValue("coverage", "false");
-
- ITestInvocationListener listener =
- mInstrumentationTest.addJavaCoverageListenerIfEnabled(mMockListener);
- assertThat(listener).isSameAs(mMockListener);
-
- listener = mInstrumentationTest.addGcovCoverageListenerIfEnabled(mMockListener);
- assertThat(listener).isSameAs(mMockListener);
- }
-
/** Test normal run scenario when {@link IMetricCollector} are specified. */
@Test
public void testRun_withCollectors() throws DeviceNotAvailableException {
diff --git a/tests/src/com/android/tradefed/testtype/NoisyDryRunTestTest.java b/tests/src/com/android/tradefed/testtype/NoisyDryRunTestTest.java
index 2c3d663..2b46263 100644
--- a/tests/src/com/android/tradefed/testtype/NoisyDryRunTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/NoisyDryRunTestTest.java
@@ -366,6 +366,30 @@
verifyMocks();
}
+ @Test
+ public void testRun_withDelegation() throws Exception {
+ FileUtil.writeToFile("tf/fake --delegated-tf .\n" + "tf/fake", mFile);
+ mMockListener.testRunStarted("com.android.tradefed.testtype.NoisyDryRunTest_parseFile", 1);
+ mMockListener.testStarted(anyObject());
+ mMockListener.testEnded(anyObject(), EasyMock.<HashMap<String, Metric>>anyObject());
+ mMockListener.testRunEnded(EasyMock.eq(0l), EasyMock.<HashMap<String, Metric>>anyObject());
+
+ mMockListener.testRunStarted(
+ "com.android.tradefed.testtype.NoisyDryRunTest_parseCommands", 2);
+ mMockListener.testStarted(anyObject());
+ mMockListener.testEnded(anyObject(), EasyMock.<HashMap<String, Metric>>anyObject());
+ mMockListener.testStarted(anyObject());
+ mMockListener.testEnded(anyObject(), EasyMock.<HashMap<String, Metric>>anyObject());
+ mMockListener.testRunEnded(EasyMock.eq(0l), EasyMock.<HashMap<String, Metric>>anyObject());
+ replayMocks();
+
+ NoisyDryRunTest noisyDryRunTest = new NoisyDryRunTest();
+ OptionSetter setter = new OptionSetter(noisyDryRunTest);
+ setter.setOptionValue("cmdfile", mFile.getAbsolutePath());
+ noisyDryRunTest.run(mTestInfo, mMockListener);
+ verifyMocks();
+ }
+
private void replayMocks() {
EasyMock.replay(mMockListener, mMockRunUtil);
}
diff --git a/tests/src/com/android/tradefed/testtype/binary/ExecutableTargetTestTest.java b/tests/src/com/android/tradefed/testtype/binary/ExecutableTargetTestTest.java
index ba18dc0..5c628df 100644
--- a/tests/src/com/android/tradefed/testtype/binary/ExecutableTargetTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/binary/ExecutableTargetTestTest.java
@@ -52,7 +52,6 @@
private final String testCmd2 = "cmd2";
private final String testName3 = "testName3";
private final String testCmd3 = "cmd3";
- private static final String NO_BINARY_ERROR = "Binary %s does not exist.";
private static final String ERROR_MESSAGE = "binary returned non-zero exit code.";
private ITestInvocationListener mListener = null;
@@ -376,7 +375,7 @@
/** Test split() for sharding */
@Test
- public void testShard_Split() throws DeviceNotAvailableException, ConfigurationException {
+ public void testShard_Split() throws ConfigurationException {
mExecutableTargetTest = new ExecutableTargetTest();
// Set test commands
OptionSetter setter = new OptionSetter(mExecutableTargetTest);
diff --git a/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java b/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
index de69a05..27e793b 100644
--- a/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
@@ -28,6 +28,9 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -78,6 +81,7 @@
private File mMoblyTestDir;
private File mMoblyBinary; // used by python-binaries option
private File mMoblyBinary2; // used by par-file-name option
+ private File mVenvDir;
private DeviceBuildInfo mMockBuildInfo;
@Before
@@ -87,6 +91,10 @@
mMockRunUtil = Mockito.mock(IRunUtil.class);
mMockBuildInfo = Mockito.mock(DeviceBuildInfo.class);
mSpyTest.setDevice(mMockDevice);
+
+ mVenvDir = FileUtil.createTempDir("venv");
+ new File(mVenvDir, "bin").mkdir();
+
Mockito.doReturn(mMockRunUtil).when(mSpyTest).getRunUtil();
Mockito.doReturn(DEFAULT_TIME_OUT).when(mSpyTest).getTestTimeout();
Mockito.doReturn("not_adb").when(mSpyTest).getAdbPath();
@@ -99,6 +107,7 @@
@After
public void tearDown() throws Exception {
FileUtil.recursiveDelete(mMoblyTestDir);
+ FileUtil.recursiveDelete(mVenvDir);
}
@Test
@@ -216,6 +225,71 @@
}
@Test
+ public void testRun_shouldActivateVenvAndCleanUp_whenVenvIsSet() throws Exception {
+ Mockito.when(mMockBuildInfo.getFile(eq("VIRTUAL_ENV"))).thenReturn(mVenvDir);
+ OptionSetter setter = new OptionSetter(mSpyTest);
+ setter.setOptionValue("python-binaries", mMoblyBinary.getAbsolutePath());
+ File testResult = new File(mSpyTest.getLogDirAbsolutePath(), TEST_RESULT_FILE_NAME);
+ Mockito.when(
+ mMockRunUtil.runTimedCmd(
+ anyLong(),
+ anyString(),
+ eq("--"),
+ contains("--device_serial="),
+ contains("--log_path=")))
+ .thenAnswer(
+ new Answer<CommandResult>() {
+ @Override
+ public CommandResult answer(InvocationOnMock invocation)
+ throws Throwable {
+ FileUtils.createFile(testResult, "");
+ FileUtils.createFile(
+ new File(mSpyTest.getLogDirAbsolutePath(), "log"),
+ "log content");
+ return new CommandResult(CommandStatus.SUCCESS);
+ }
+ });
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ result.setStdout(
+ "Name: pip\nLocation: "
+ + new File(mVenvDir.getAbsolutePath(), "lib/python3.8/site-packages"));
+ Mockito.when(mMockRunUtil.runTimedCmd(anyLong(), anyString(), eq("show"), eq("pip")))
+ .thenReturn(result);
+
+ mSpyTest.run(Mockito.mock(ITestInvocationListener.class));
+
+ verify(mSpyTest.getRunUtil(), times(1))
+ .setEnvVariable(eq("VIRTUAL_ENV"), eq(mVenvDir.getAbsolutePath()));
+ assertFalse(mVenvDir.exists());
+ }
+
+ @Test
+ public void testRun_shouldNotActivateVenv_whenVenvIsNotSet() throws Exception {
+ FileUtil.recursiveDelete(mVenvDir);
+ OptionSetter setter = new OptionSetter(mSpyTest);
+ setter.setOptionValue("python-binaries", mMoblyBinary.getAbsolutePath());
+ File testResult = new File(mSpyTest.getLogDirAbsolutePath(), TEST_RESULT_FILE_NAME);
+ Mockito.when(mMockRunUtil.runTimedCmd(anyLong(), any()))
+ .thenAnswer(
+ new Answer<CommandResult>() {
+ @Override
+ public CommandResult answer(InvocationOnMock invocation)
+ throws Throwable {
+ FileUtils.createFile(testResult, "");
+ FileUtils.createFile(
+ new File(mSpyTest.getLogDirAbsolutePath(), "log"),
+ "log content");
+ return new CommandResult(CommandStatus.SUCCESS);
+ }
+ });
+
+ mSpyTest.run(Mockito.mock(ITestInvocationListener.class));
+
+ verify(mSpyTest.getRunUtil(), never())
+ .setEnvVariable(eq("VIRTUAL_ENV"), eq(mVenvDir.getAbsolutePath()));
+ }
+
+ @Test
public void testBuildCommandLineArrayWithOutConfig() throws Exception {
Mockito.doNothing().when(mSpyTest).reportLogs(any(), any());
Mockito.doReturn(DEVICE_SERIAL).when(mMockDevice).getSerialNumber();
@@ -262,7 +336,11 @@
Mockito.doNothing().when(mSpyTest).reportLogs(any(), any());
mMockSummaryInputStream = Mockito.mock(InputStream.class);
mMockParser = Mockito.mock(MoblyYamlResultParser.class);
- mSpyTest.processYamlTestResults(mMockSummaryInputStream, mMockParser);
+ mSpyTest.processYamlTestResults(
+ mMockSummaryInputStream,
+ mMockParser,
+ Mockito.mock(ITestInvocationListener.class),
+ "runName");
verify(mMockParser, times(1)).parse(mMockSummaryInputStream);
}
diff --git a/tests/src/com/android/tradefed/testtype/rust/RustBinaryHostTestTest.java b/tests/src/com/android/tradefed/testtype/rust/RustBinaryHostTestTest.java
index 782eac0..22e3c1a 100644
--- a/tests/src/com/android/tradefed/testtype/rust/RustBinaryHostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/rust/RustBinaryHostTestTest.java
@@ -81,6 +81,24 @@
return newCommandResult(CommandStatus.SUCCESS, stderr, stdout);
}
+ // shared with RustBinaryTestTest
+ static String runListOutput(int numTests) {
+ String listOutput = "";
+ for (int i = 1; i <= numTests; i++) {
+ listOutput += "test_case_" + i + ": test\n";
+ }
+ return listOutput + numTests + " tests, 0 benchmarks";
+ }
+
+ // shared with RustBinaryTestTest
+ static String runListOutput(String[] tests) {
+ String listOutput = "";
+ for (String name : tests) {
+ listOutput += name + ": test\n";
+ }
+ return listOutput + tests.length + " tests, 0 benchmarks";
+ }
+
/** Add mocked call "binary --list" to count the number of tests. */
private void mockCountTests(File binary, int numOfTest) throws Exception {
EasyMock.expect(
@@ -88,7 +106,7 @@
EasyMock.anyLong(),
EasyMock.eq(binary.getAbsolutePath()),
EasyMock.eq("--list")))
- .andReturn(successResult("", numOfTest + " tests, 0 benchmarks"));
+ .andReturn(successResult("", runListOutput(numOfTest)));
}
/** Add mocked testRunStarted call to the listener. */
@@ -184,7 +202,6 @@
mockListenerLog(binary);
CommandResult res = newCommandResult(CommandStatus.EXCEPTION, "Err.", "Exception.");
mockTestRunExpect(binary, res);
- mMockListener.testRunFailed((String) EasyMock.anyObject());
mMockListener.testRunFailed((FailureDescription) EasyMock.anyObject());
mockTestRunEnded();
callReplayRunVerify();
@@ -261,7 +278,7 @@
EasyMock.eq("--skip"),
EasyMock.eq("Long"),
EasyMock.eq("--list")))
- .andReturn(successResult("", "9 tests, 0 benchmarks"));
+ .andReturn(successResult("", runListOutput(9)));
mockListenerStarted(binary, 9);
mockListenerLog(binary);
CommandResult res = successResult("", resultCount(6, 1, 2));
@@ -289,24 +306,21 @@
try {
OptionSetter setter = new OptionSetter(mTest);
setter.setOptionValue("test-file", binary.getAbsolutePath());
- setter.setOptionValue("exclude-filter", "NotMe");
- setter.setOptionValue("include-filter", "OnlyMe");
+ setter.setOptionValue("exclude-filter", "MyTest#NotMe");
+ setter.setOptionValue("include-filter", "MyTest#OnlyMe");
setter.setOptionValue("exclude-filter", "Other");
- setter.setOptionValue("include-filter", "Me2");
// We always pass the include-filter before exclude-filter strings.
- // Multiple include filters are accepted but all except the 1st are ignored.
EasyMock.expect(
mMockRunUtil.runTimedCmdSilently(
EasyMock.anyLong(),
EasyMock.eq(binary.getAbsolutePath()),
EasyMock.eq("OnlyMe"),
- EasyMock.eq("Me2"),
EasyMock.eq("--skip"),
EasyMock.eq("NotMe"),
EasyMock.eq("--skip"),
EasyMock.eq("Other"),
EasyMock.eq("--list")))
- .andReturn(successResult("", "3 tests, 0 benchmarks"));
+ .andReturn(successResult("", runListOutput(3)));
mockListenerStarted(binary, 3);
mockListenerLog(binary);
@@ -316,6 +330,77 @@
EasyMock.anyLong(),
EasyMock.eq(binary.getAbsolutePath()),
EasyMock.eq("OnlyMe"),
+ EasyMock.eq("--skip"),
+ EasyMock.eq("NotMe"),
+ EasyMock.eq("--skip"),
+ EasyMock.eq("Other")))
+ .andReturn(res);
+
+ mockTestRunEnded();
+ callReplayRunVerify();
+ } finally {
+ FileUtil.deleteFile(binary);
+ }
+ }
+
+ /** Test multiple include and exclude filters. */
+ @Test
+ public void testMultipleIncludeExcludeFilter() throws Exception {
+ File binary = FileUtil.createTempFile("rust-dir", "");
+ try {
+ OptionSetter setter = new OptionSetter(mTest);
+ setter.setOptionValue("test-file", binary.getAbsolutePath());
+ setter.setOptionValue("exclude-filter", "NotMe");
+ setter.setOptionValue("include-filter", "MyTest#OnlyMe");
+ setter.setOptionValue("exclude-filter", "MyTest#Other");
+ setter.setOptionValue("include-filter", "Me2");
+ // Multiple include filters are run one by one with --list.
+ String[] selection1 = new String[] {"test1", "test2"};
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmdSilently(
+ EasyMock.anyLong(),
+ EasyMock.eq(binary.getAbsolutePath()),
+ EasyMock.eq("OnlyMe"),
+ EasyMock.eq("--skip"),
+ EasyMock.eq("NotMe"),
+ EasyMock.eq("--skip"),
+ EasyMock.eq("Other"),
+ EasyMock.eq("--list")))
+ .andReturn(successResult("", runListOutput(selection1)));
+ String[] selection2 = new String[] {"test2", "test3", "test4"};
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmdSilently(
+ EasyMock.anyLong(),
+ EasyMock.eq(binary.getAbsolutePath()),
+ EasyMock.eq("Me2"),
+ EasyMock.eq("--skip"),
+ EasyMock.eq("NotMe"),
+ EasyMock.eq("--skip"),
+ EasyMock.eq("Other"),
+ EasyMock.eq("--list")))
+ .andReturn(successResult("", runListOutput(selection2)));
+ // Union of selection1 and selection2 has 4 tests.
+ mockListenerStarted(binary, 4);
+
+ // Multiple include filters are run one by one.
+ mockListenerLog(binary);
+ CommandResult res = successResult("", resultCount(2, 0, 0));
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(),
+ EasyMock.eq(binary.getAbsolutePath()),
+ EasyMock.eq("OnlyMe"),
+ EasyMock.eq("--skip"),
+ EasyMock.eq("NotMe"),
+ EasyMock.eq("--skip"),
+ EasyMock.eq("Other")))
+ .andReturn(res);
+ mockListenerLog(binary);
+ res = successResult("", resultCount(3, 0, 0));
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(),
+ EasyMock.eq(binary.getAbsolutePath()),
EasyMock.eq("Me2"),
EasyMock.eq("--skip"),
EasyMock.eq("NotMe"),
diff --git a/tests/src/com/android/tradefed/testtype/rust/RustBinaryTestTest.java b/tests/src/com/android/tradefed/testtype/rust/RustBinaryTestTest.java
index a633ed1..3d7d450 100644
--- a/tests/src/com/android/tradefed/testtype/rust/RustBinaryTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/rust/RustBinaryTestTest.java
@@ -28,9 +28,7 @@
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.LogDataType;
import com.android.tradefed.testtype.coverage.CoverageOptions;
-import com.android.tradefed.util.FileUtil;
import org.easymock.EasyMock;
import org.junit.Before;
@@ -38,10 +36,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.io.File;
-import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.concurrent.TimeUnit;
/** Unit tests for {@link RustBinaryTest}. */
@@ -97,6 +92,14 @@
EasyMock.verify(mMockInvocationListener, mMockITestDevice, mMockReceiver);
}
+ private String runListOutput(int numTests) {
+ return RustBinaryHostTestTest.runListOutput(numTests);
+ }
+
+ private String runListOutput(String[] tests) {
+ return RustBinaryHostTestTest.runListOutput(tests);
+ }
+
/** Add mocked Call "path --list" to count the number of tests. */
private void mockCountTests(String path, String result) throws DeviceNotAvailableException {
EasyMock.expect(mMockITestDevice.executeShellCommand(path + " --list")).andReturn(result);
@@ -218,12 +221,12 @@
EasyMock.expect(mMockITestDevice.isDirectory(testPath2)).andReturn(false);
EasyMock.expect(mMockITestDevice.isExecutable(testPath2)).andReturn(true);
- mockCountTests(testPath1, "test1\n3 tests, 0 benchmarks\n");
+ mockCountTests(testPath1, runListOutput(3));
mockTestRunStarted("test1", 3);
mockShellCommand(test1);
mockTestRunEnded();
- mockCountTests(testPath2, "test2\n7 tests, 0 benchmarks\n");
+ mockCountTests(testPath2, runListOutput(7));
mockTestRunStarted("test2", 7);
mockShellCommand(test2);
mockTestRunEnded();
@@ -247,7 +250,7 @@
EasyMock.expect(mMockITestDevice.isDirectory(modulePath)).andReturn(false);
EasyMock.expect(mMockITestDevice.isExecutable(modulePath)).andReturn(true);
- mockCountTests(modulePath, "moduleTest\n1 test, 0 benchmarks\n");
+ mockCountTests(modulePath, runListOutput(1));
mockTestRunStarted("test1", 1);
mockShellCommand(modulePath);
mockTestRunEnded();
@@ -281,216 +284,20 @@
String[] files2 = new String[] {"test1"};
EasyMock.expect(mMockITestDevice.getChildren(subDirPath)).andReturn(files2);
- mockCountTests(test1Path, "test1\n5 tests, 0 benchmarks\n");
+ mockCountTests(test1Path, runListOutput(5));
mockTestRunStarted("test1", 5);
mockShellCommand(test1Path);
mockTestRunEnded();
callReplayRunVerify();
}
- /** Test cross-process coverage dump for all native processes */
- @Test
- public void testNativeCoverageAllProcesses() throws Exception {
- mCoverageOptionsSetter.setOptionValue("coverage", "true");
- mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "GCOV");
- mCoverageOptionsSetter.setOptionValue("coverage-flush", "true");
-
- final String testPath = RustBinaryTest.DEFAULT_TEST_PATH;
- final String test1 = "test1";
- final String test2 = "test2";
- final String testPath1 = String.format("%s/%s", testPath, test1);
- final String testPath2 = String.format("%s/%s", testPath, test2);
- final String coverageTarPath = "/data/misc/trace/coverage.tar";
-
- MockFileUtil.setMockDirContents(mMockITestDevice, testPath, test1, test2);
- EasyMock.expect(mMockITestDevice.enableAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.enableAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.executeShellCommand("mkdir /data/misc/trace/testcoverage"))
- .andReturn("");
- EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 -1")).andReturn("");
- EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 -1")).andReturn("");
- EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 -1")).andReturn("");
- // Wait up to 5 minutes for the device to be available after flushing coverage data.
- mMockITestDevice.waitForDeviceAvailable(5 * 60 * 1000);
- mMockITestDevice.waitForDeviceAvailable(5 * 60 * 1000);
- mMockITestDevice.waitForDeviceAvailable(5 * 60 * 1000);
- EasyMock.expect(mMockITestDevice.executeShellCommand("rm -rf /data/misc/trace/*"))
- .andReturn("");
- EasyMock.expect(mMockITestDevice.doesFileExist(testPath)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(testPath)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(testPath1)).andReturn(false);
- // report the file as executable
- EasyMock.expect(mMockITestDevice.isExecutable(testPath1)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(testPath2)).andReturn(false);
- // report the file as executable
- EasyMock.expect(mMockITestDevice.isExecutable(testPath2)).andReturn(true);
- EasyMock.expect(
- mMockITestDevice.executeShellCommand(
- "find /data/misc/trace -name '*.gcda' | tar -cvf"
- + " /data/misc/trace/coverage.tar -T -"))
- .andReturn("");
- EasyMock.expect(
- mMockITestDevice.executeShellCommand(
- "find /data/misc/trace -name '*.gcda' | tar -cvf"
- + " /data/misc/trace/coverage.tar -T -"))
- .andReturn("");
- File tmpFile1 = FileUtil.createTempFile("coverage", ".tar");
- EasyMock.expect(mMockITestDevice.pullFile(coverageTarPath)).andReturn(tmpFile1);
- File tmpFile2 = FileUtil.createTempFile("coverage", ".tar");
- EasyMock.expect(mMockITestDevice.pullFile(coverageTarPath)).andReturn(tmpFile2);
- mMockITestDevice.deleteFile(coverageTarPath);
- mMockITestDevice.deleteFile(coverageTarPath);
- mMockInvocationListener.testLog(
- EasyMock.eq("null_native_runtime_coverage"),
- EasyMock.eq(LogDataType.NATIVE_COVERAGE),
- EasyMock.anyObject());
- mMockInvocationListener.testLog(
- EasyMock.eq("null_native_runtime_coverage"),
- EasyMock.eq(LogDataType.NATIVE_COVERAGE),
- EasyMock.anyObject());
- EasyMock.expect(
- mMockITestDevice.executeShellCommand(
- "find /data/misc/trace -name '*.gcda' -delete"))
- .andReturn("");
- EasyMock.expect(
- mMockITestDevice.executeShellCommand(
- "find /data/misc/trace -name '*.gcda' -delete"))
- .andReturn("");
- EasyMock.expect(mMockITestDevice.enableAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
-
- String[] files = new String[] {"test1", "test2"};
- EasyMock.expect(mMockITestDevice.getChildren(testPath)).andReturn(files);
-
- mockCountTests(
- "GCOV_PREFIX=/data/misc/trace/testcoverage " + testPath1,
- "test1\n1 test, 0 benchmarks\n");
- mockTestRunStarted("test1", 1);
- mockShellCommand(test1);
- mockTestRunEnded();
- mockCountTests(
- "GCOV_PREFIX=/data/misc/trace/testcoverage " + testPath2,
- "test2\n1 test, 0 benchmarks\n");
- mockTestRunStarted("test2", 1);
- mockShellCommand(test2);
- mockTestRunEnded();
- callReplayRunVerify();
- }
-
- /** Test cross-process coverage dump for specific processes */
- @Test
- public void testNativeCoverageSpecificProcesses() throws Exception {
- final List<String> processNames = new ArrayList<>();
- processNames.add("init");
- processNames.add("surfaceflinger");
-
- mCoverageOptionsSetter.setOptionValue("coverage", "true");
- mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "GCOV");
- mCoverageOptionsSetter.setOptionValue("coverage-flush", "true");
- for (String processName : processNames) {
- mCoverageOptionsSetter.setOptionValue("coverage-processes", processName);
- }
-
- final String testPath = RustBinaryTest.DEFAULT_TEST_PATH;
- final String test1 = "test1";
- final String test2 = "test2";
- final String testPath1 = String.format("%s/%s", testPath, test1);
- final String testPath2 = String.format("%s/%s", testPath, test2);
- final String coverageTarPath = "/data/misc/trace/coverage.tar";
-
- MockFileUtil.setMockDirContents(mMockITestDevice, testPath, test1, test2);
- EasyMock.expect(mMockITestDevice.enableAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.enableAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.executeShellCommand("mkdir /data/misc/trace/testcoverage"))
- .andReturn("");
- // Get the pids to flush coverage data.
- EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.getProcessPid(processNames.get(0))).andReturn("1");
- EasyMock.expect(mMockITestDevice.getProcessPid(processNames.get(0))).andReturn("1");
- EasyMock.expect(mMockITestDevice.getProcessPid(processNames.get(0))).andReturn("1");
- EasyMock.expect(mMockITestDevice.getProcessPid(processNames.get(1))).andReturn("1000");
- EasyMock.expect(mMockITestDevice.getProcessPid(processNames.get(1))).andReturn("1000");
- EasyMock.expect(mMockITestDevice.getProcessPid(processNames.get(1))).andReturn("1000");
- EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 1 1000")).andReturn("");
- EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 1 1000")).andReturn("");
- EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 1 1000")).andReturn("");
- // Wait up to 5 minutes for the device to be available after flushing coverage data.
- mMockITestDevice.waitForDeviceAvailable(5 * 60 * 1000);
- mMockITestDevice.waitForDeviceAvailable(5 * 60 * 1000);
- mMockITestDevice.waitForDeviceAvailable(5 * 60 * 1000);
- EasyMock.expect(mMockITestDevice.executeShellCommand("rm -rf /data/misc/trace/*"))
- .andReturn("");
- EasyMock.expect(mMockITestDevice.doesFileExist(testPath)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(testPath)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(testPath1)).andReturn(false);
- // report the file as executable
- EasyMock.expect(mMockITestDevice.isExecutable(testPath1)).andReturn(true);
- EasyMock.expect(mMockITestDevice.isDirectory(testPath2)).andReturn(false);
- // report the file as executable
- EasyMock.expect(mMockITestDevice.isExecutable(testPath2)).andReturn(true);
- EasyMock.expect(
- mMockITestDevice.executeShellCommand(
- "find /data/misc/trace -name '*.gcda' | tar -cvf"
- + " /data/misc/trace/coverage.tar -T -"))
- .andReturn("");
- EasyMock.expect(
- mMockITestDevice.executeShellCommand(
- "find /data/misc/trace -name '*.gcda' | tar -cvf"
- + " /data/misc/trace/coverage.tar -T -"))
- .andReturn("");
- File tmpFile1 = FileUtil.createTempFile("coverage", ".tar");
- EasyMock.expect(mMockITestDevice.pullFile(coverageTarPath)).andReturn(tmpFile1);
- File tmpFile2 = FileUtil.createTempFile("coverage", ".tar");
- EasyMock.expect(mMockITestDevice.pullFile(coverageTarPath)).andReturn(tmpFile2);
- mMockITestDevice.deleteFile(coverageTarPath);
- mMockITestDevice.deleteFile(coverageTarPath);
- mMockInvocationListener.testLog(
- EasyMock.eq("null_native_runtime_coverage"),
- EasyMock.eq(LogDataType.NATIVE_COVERAGE),
- EasyMock.anyObject());
- mMockInvocationListener.testLog(
- EasyMock.eq("null_native_runtime_coverage"),
- EasyMock.eq(LogDataType.NATIVE_COVERAGE),
- EasyMock.anyObject());
- EasyMock.expect(
- mMockITestDevice.executeShellCommand(
- "find /data/misc/trace -name '*.gcda' -delete"))
- .andReturn("");
- EasyMock.expect(
- mMockITestDevice.executeShellCommand(
- "find /data/misc/trace -name '*.gcda' -delete"))
- .andReturn("");
- EasyMock.expect(mMockITestDevice.enableAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
-
- String[] files = new String[] {"test1", "test2"};
- EasyMock.expect(mMockITestDevice.getChildren(testPath)).andReturn(files);
-
- mockCountTests(
- "GCOV_PREFIX=/data/misc/trace/testcoverage " + testPath1,
- "test1\n1 test, 0 benchmarks\n");
- mockTestRunStarted("test1", 1);
- mockShellCommand(test1);
- mockTestRunEnded();
- mockCountTests(
- "GCOV_PREFIX=/data/misc/trace/testcoverage " + testPath2,
- "test2\n1 test, 0 benchmarks\n");
- mockTestRunStarted("test2", 1);
- mockShellCommand(test2);
- mockTestRunEnded();
- callReplayRunVerify();
- }
-
/**
* Helper function to do the actual filtering test.
*
* @param filterString The string to search for in the Mock, to verify filtering was called
* @throws DeviceNotAvailableException
*/
- private void doTestFilter(String filterString) throws DeviceNotAvailableException {
+ private void doTestFilter(String[] filterStrings) throws DeviceNotAvailableException {
final String testPath = RustBinaryTest.DEFAULT_TEST_PATH;
final String test1 = "test1";
final String testPath1 = String.format("%s/%s", testPath, test1);
@@ -504,9 +311,13 @@
EasyMock.expect(mMockITestDevice.isDirectory(testPath1)).andReturn(false);
EasyMock.expect(mMockITestDevice.isExecutable(testPath1)).andReturn(true);
- mockCountTests(testPath1 + filterString, "test1\n3 tests, 0 benchmarks\n");
+ for (String filter : filterStrings) {
+ mockCountTests(testPath1 + filter, runListOutput(3));
+ }
mockTestRunStarted("test1", 3);
- mockShellCommand(test1 + filterString);
+ for (String filter : filterStrings) {
+ mockShellCommand(test1 + filter);
+ }
mockTestRunEnded();
callReplayRunVerify();
}
@@ -516,8 +327,8 @@
public void testExcludeFilter() throws Exception {
OptionSetter setter = new OptionSetter(mRustBinaryTest);
setter.setOptionValue("exclude-filter", "NotMe");
- setter.setOptionValue("exclude-filter", "Long");
- doTestFilter(" --skip NotMe --skip Long");
+ setter.setOptionValue("exclude-filter", "MyTest#Long");
+ doTestFilter(new String[] {" --skip NotMe --skip Long"});
}
/** Test both include- and exclude-filter options. */
@@ -525,11 +336,24 @@
public void testIncludeExcludeFilter() throws Exception {
OptionSetter setter = new OptionSetter(mRustBinaryTest);
setter.setOptionValue("exclude-filter", "NotMe2");
- setter.setOptionValue("include-filter", "OnlyMe");
+ setter.setOptionValue("include-filter", "MyTest#OnlyMe");
+ setter.setOptionValue("exclude-filter", "MyTest#other");
+ // Include filters are passed before exclude filters.
+ doTestFilter(new String[] {" OnlyMe --skip NotMe2 --skip other"});
+ }
+
+ /** Test multiple include- and exclude-filter options. */
+ @Test
+ public void testMultipleIncludeExcludeFilter() throws Exception {
+ OptionSetter setter = new OptionSetter(mRustBinaryTest);
+ setter.setOptionValue("exclude-filter", "MyTest#NotMe2");
+ setter.setOptionValue("include-filter", "MyTest#OnlyMe");
setter.setOptionValue("exclude-filter", "other");
setter.setOptionValue("include-filter", "Me2");
- // Include filters are passed before exclude filters.
- // Multiple include filters are accepted, but all except the 1st are ignored.
- doTestFilter(" OnlyMe Me2 --skip NotMe2 --skip other");
+ // Multiple include filters are run one by one.
+ doTestFilter(
+ new String[] {
+ " OnlyMe --skip NotMe2 --skip other", " Me2 --skip NotMe2 --skip other"
+ });
}
}
diff --git a/tests/src/com/android/tradefed/testtype/rust/RustTestBaseTest.java b/tests/src/com/android/tradefed/testtype/rust/RustTestBaseTest.java
index a7a1770..137434b 100644
--- a/tests/src/com/android/tradefed/testtype/rust/RustTestBaseTest.java
+++ b/tests/src/com/android/tradefed/testtype/rust/RustTestBaseTest.java
@@ -15,10 +15,7 @@
*/
package com.android.tradefed.testtype.rust;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
import com.android.tradefed.testtype.ITestFilterReceiver;
@@ -26,7 +23,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.text.ParseException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -79,47 +75,4 @@
runner.clearExcludeFilters();
assertEquals(set0, runner.getExcludeFilters());
}
-
- /** Test parsing of various forms of Rust test list summary output. */
- @Test
- public void testParseTestListSummaries() throws Exception {
- assertEquals(0, RustTestBase.parseTestListCount(new String[] {"0 tests, 0 benchmarks"}));
- assertEquals(1, RustTestBase.parseTestListCount(new String[] {"1 test, 0 benchmarks"}));
- assertEquals(0, RustTestBase.parseTestListCount(new String[] {"0 tests, 1 benchmark"}));
- assertEquals(2, RustTestBase.parseTestListCount(new String[] {"2 tests, 0 benchmarks"}));
- assertEquals(2, RustTestBase.parseTestListCount(new String[] {"2 tests, 1 benchmark"}));
- assertEquals(2, RustTestBase.parseTestListCount(new String[] {"2 tests, 2 benchmarks"}));
- }
-
- /** Test parsing of a real Rust test list. */
- @Test
- public void testParseTestListRealOutput() throws Exception {
- String[] contents = readInFile(RUST_LIST_FILE_1);
- assertEquals(42, RustTestBase.parseTestListCount(contents));
- }
-
- /** Test parsing of an empty Rust test list. */
- @Test
- public void testParseTestListEmpty() throws Exception {
- try {
- RustTestBase.parseTestListCount(new String[] {});
- fail("Should have thrown an exception.");
- } catch (ParseException e) {
- assertThat(e).hasMessageThat().contains("Test did not return any output");
- }
- }
-
- /** Test parsing of a malformed or non-standard Rust test list. */
- @Test
- public void testParseTestListMalformed() throws Exception {
- String[] contents = {
- "some other output", "that does not contain a summary line",
- };
- try {
- RustTestBase.parseTestListCount(contents);
- fail("Should have thrown an exception.");
- } catch (ParseException e) {
- assertThat(e).hasMessageThat().contains("Could not match total");
- }
- }
}
diff --git a/tests/src/com/android/tradefed/testtype/rust/RustTestResultParserTest.java b/tests/src/com/android/tradefed/testtype/rust/RustTestResultParserTest.java
index d5ed861..af6bafd 100644
--- a/tests/src/com/android/tradefed/testtype/rust/RustTestResultParserTest.java
+++ b/tests/src/com/android/tradefed/testtype/rust/RustTestResultParserTest.java
@@ -32,6 +32,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.Arrays;
import java.util.HashMap;
/** Unit tests for {@link RustTestResultParser}. */
@@ -108,6 +109,7 @@
replay(mMockListener);
mParser.processNewLines(contents);
+ mParser.done();
verify(mMockListener);
}
@@ -126,6 +128,7 @@
(String) EasyMock.anyObject());
replay(mMockListener);
mParser.processNewLines(contents);
+ mParser.done();
verify(mMockListener);
}
@@ -141,6 +144,27 @@
EasyMock.eq(new TestDescription("test", "make_sure_no_proc_macro")));
replay(mMockListener);
mParser.processNewLines(contents);
+ mParser.done();
+ verify(mMockListener);
+ }
+
+ /**
+ * Tests may not return all their output in a single call to processNewLines. This tests that we
+ * properly parse output split across several calls.
+ */
+ @Test
+ public void testParsePartialOutput() {
+ String[] contents = readInFile(RUST_OUTPUT_FILE_1);
+ for (int i = 0; i < 10; i++) {
+ mMockListener.testStarted(EasyMock.anyObject());
+ mMockListener.testEnded(
+ EasyMock.anyObject(), EasyMock.<HashMap<String, Metric>>anyObject());
+ }
+ replay(mMockListener);
+ mParser.processNewLines(Arrays.copyOfRange(contents, 0, 4));
+ mParser.processNewLines(Arrays.copyOfRange(contents, 4, 7));
+ mParser.processNewLines(Arrays.copyOfRange(contents, 4, contents.length));
+ mParser.done();
verify(mMockListener);
}
}
diff --git a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
index 3cee111..1050460 100644
--- a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
@@ -26,6 +26,7 @@
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.ITestDevice;
@@ -263,13 +264,22 @@
private GranularRetriableTestWrapper createGranularTestWrapper(
IRemoteTest test, int maxRunCount) throws Exception {
- return createGranularTestWrapper(test, maxRunCount, new ArrayList<>());
+ return createGranularTestWrapper(test, maxRunCount, new ArrayList<>(), null);
}
private GranularRetriableTestWrapper createGranularTestWrapper(
IRemoteTest test, int maxRunCount, List<IMetricCollector> collectors) throws Exception {
+ return createGranularTestWrapper(test, maxRunCount, collectors, null);
+ }
+
+ private GranularRetriableTestWrapper createGranularTestWrapper(
+ IRemoteTest test,
+ int maxRunCount,
+ List<IMetricCollector> collectors,
+ ModuleDefinition module)
+ throws Exception {
GranularRetriableTestWrapper granularTestWrapper =
- new GranularRetriableTestWrapper(test, null, null, null, maxRunCount);
+ new GranularRetriableTestWrapper(test, module, null, null, null, maxRunCount);
granularTestWrapper.setModuleId("test module");
granularTestWrapper.setMarkTestsSkipped(false);
granularTestWrapper.setMetricCollectors(collectors);
@@ -904,6 +914,101 @@
EasyMock.verify(mMockDevice, mMockDevice2);
}
+ /** Test to reset multi-devices at the last intra-module retry. */
+ @Test
+ public void testIntraModuleRun_resetMultiDevicesAtLastIntraModuleRetry() throws Exception {
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("reset-at-last-retry", "true");
+ setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModuleInvocationContext);
+ FakeTest test = new FakeTest();
+ test.setRunFailure("I failed!");
+ ITestDevice noneAVDDevice = EasyMock.createMock(ITestDevice.class);
+
+ RemoteAndroidVirtualDevice avdDevice = Mockito.mock(RemoteAndroidVirtualDevice.class);
+ Mockito.when(avdDevice.powerwashGce()).thenReturn(true);
+
+ ModuleDefinition module = Mockito.mock(ModuleDefinition.class);
+ // Should call suite level preparers.
+ Mockito.when(module.runPreparation(true)).thenReturn(null);
+
+ mModuleInvocationContext.addAllocatedDevice("default-device1", noneAVDDevice);
+ mModuleInvocationContext.addAllocatedDevice("default-device2", avdDevice);
+ GranularRetriableTestWrapper granularTestWrapper =
+ createGranularTestWrapper(test, 3, new ArrayList<>(), module);
+ granularTestWrapper.setRetryDecision(decision);
+ EasyMock.expect(noneAVDDevice.getIDevice())
+ .andStubReturn(EasyMock.createMock(IDevice.class));
+ EasyMock.expect(noneAVDDevice.getSerialNumber()).andStubReturn("device-1");
+
+ EasyMock.replay(noneAVDDevice);
+ granularTestWrapper.run(mModuleInfo, new CollectingTestListener());
+ EasyMock.verify(noneAVDDevice);
+ }
+
+ /** Test to reset device at the last intra-module retry failed due to preparer failure. */
+ @Test
+ public void testIntraModuleRun_resetFailed_preparerFailure() throws Exception {
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("reset-at-last-retry", "true");
+ setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModuleInvocationContext);
+ FakeTest test = new FakeTest();
+ test.setRunFailure("I failed!");
+
+ RemoteAndroidVirtualDevice avdDevice = Mockito.mock(RemoteAndroidVirtualDevice.class);
+ Mockito.when(avdDevice.powerwashGce()).thenReturn(true);
+
+ ModuleDefinition module = Mockito.mock(ModuleDefinition.class);
+ // Suite level preparers failed.
+ Mockito.when(module.runPreparation(true)).thenReturn(new RuntimeException());
+
+ mModuleInvocationContext.addAllocatedDevice("default-device2", avdDevice);
+ GranularRetriableTestWrapper granularTestWrapper =
+ createGranularTestWrapper(test, 3, new ArrayList<>(), module);
+ granularTestWrapper.setRetryDecision(decision);
+
+ try {
+ granularTestWrapper.run(mModuleInfo, new CollectingTestListener());
+ fail("Exception should be raised when reset is failed.");
+ } catch (DeviceNotAvailableException e) {
+ assertTrue(e.getMessage().startsWith("Failed to reset devices before retry: "));
+ }
+ }
+
+ /** Test to reset device at the last intra-module retry failed due to reset failure. */
+ @Test
+ public void testIntraModuleRun_resetFailed_powerwashFailure() throws Exception {
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("reset-at-last-retry", "true");
+ setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModuleInvocationContext);
+ FakeTest test = new FakeTest();
+ test.setRunFailure("I failed!");
+
+ RemoteAndroidVirtualDevice device = Mockito.mock(RemoteAndroidVirtualDevice.class);
+ Mockito.when(device.powerwashGce()).thenReturn(false);
+ Mockito.when(device.getSerialNumber()).thenReturn("device1");
+
+ test.setDevice(device);
+ mModuleInvocationContext.addAllocatedDevice("default-device1", device);
+ GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
+ granularTestWrapper.setRetryDecision(decision);
+
+ try {
+ granularTestWrapper.run(mModuleInfo, new CollectingTestListener());
+ fail("Exception should be raised when reset is failed.");
+ } catch (DeviceNotAvailableException e) {
+ assertEquals("Failed to powerwash device: device1", e.getMessage());
+ }
+ }
+
/** Collector that track if it was called or not */
public static class CalledMetricCollector extends BaseDeviceMetricCollector {
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index 592a71d..876ba74 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -59,6 +59,7 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.MultiFailureDescription;
import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.retry.BaseRetryDecision;
import com.android.tradefed.retry.IRetryDecision;
@@ -227,7 +228,8 @@
TestDescription test = new TestDescription(EMPTY_CONFIG, EMPTY_CONFIG);
listener.testStarted(test, 0);
if (mFailed != null) {
- listener.testFailed(test, mFailed);
+ listener.testFailed(
+ test, FailureDescription.create(mFailed, FailureStatus.TEST_FAILURE));
}
listener.testEnded(test, 5, new HashMap<String, Metric>());
} finally {
@@ -399,7 +401,8 @@
TestDescription test = new TestDescription(EMPTY_CONFIG, EMPTY_CONFIG);
listener.testStarted(test, 0);
if (testFailed) {
- listener.testFailed(test, message);
+ listener.testFailed(
+ test, FailureDescription.create(message, FailureStatus.TEST_FAILURE));
}
listener.testEnded(test, 5, new HashMap<String, Metric>());
listener.testRunEnded(EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
@@ -415,7 +418,9 @@
TestDescription test = new TestDescription(EMPTY_CONFIG, EMPTY_CONFIG);
listener.testStarted(test, 0);
if (testFailed) {
- listener.testFailed(test, mTestFailedMessage);
+ listener.testFailed(
+ test,
+ FailureDescription.create(mTestFailedMessage, FailureStatus.TEST_FAILURE));
}
listener.testEnded(test, 5, new HashMap<String, Metric>());
listener.testRunEnded(
@@ -661,7 +666,9 @@
fake.setTest(
new StubCollectingTest(
new DeviceUnresponsiveException(
- "unresponsive", "serial")));
+ "unresponsive",
+ "serial",
+ DeviceErrorIdentifier.DEVICE_UNRESPONSIVE)));
testConfig.put(TEST_CONFIG_NAME, fake);
} catch (ConfigurationException e) {
CLog.e(e);
@@ -683,7 +690,8 @@
mMockListener.testRunStarted(
EasyMock.eq(TEST_CONFIG_NAME), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
EasyMock.expectLastCall().times(1);
- mMockListener.testRunFailed(FailureDescription.create("unresponsive"));
+ mMockListener.testRunFailed(
+ FailureDescription.create("unresponsive", FailureStatus.LOST_SYSTEM_UNDER_TEST));
EasyMock.expect(
mMockDevice.logBugreport(
EasyMock.eq("module-test-failure-SERIAL-bugreport"),
@@ -811,11 +819,6 @@
EasyMock.expectLastCall().times(1);
Capture<FailureDescription> captured = new Capture<>();
mMockListener.testRunFailed(EasyMock.capture(captured));
- EasyMock.expect(
- mMockDevice.logBugreport(
- EasyMock.eq("module-test-failure-SERIAL-bugreport"),
- EasyMock.anyObject()))
- .andReturn(true);
mMockListener.testRunEnded(
EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
EasyMock.expectLastCall().times(1);
@@ -1604,7 +1607,9 @@
EasyMock.anyLong());
TestDescription testId = new TestDescription(EMPTY_CONFIG, EMPTY_CONFIG);
mMockListener.testStarted(testId, 0);
- mMockListener.testFailed(testId, mTestFailedMessage);
+ mMockListener.testFailed(
+ testId,
+ FailureDescription.create(mTestFailedMessage, FailureStatus.TEST_FAILURE));
mMockListener.testEnded(testId, 5, new HashMap<String, Metric>());
mMockListener.testRunEnded(
EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
index 18e3ce8..cc0819d 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
@@ -38,7 +38,6 @@
import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.ITestDevice.RecoveryMode;
-import com.android.tradefed.device.StubDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.invoker.TestInvocation;
@@ -57,6 +56,8 @@
import com.android.tradefed.result.ResultForwarder;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.retry.BaseRetryDecision;
import com.android.tradefed.retry.IRetryDecision;
import com.android.tradefed.targetprep.BaseTargetPreparer;
@@ -160,10 +161,12 @@
TestDescription test = new TestDescription(mRunName + "class", "test" + i);
listener.testStarted(test);
if (mShouldThrow && i == mNumTest / 2) {
- throw new DeviceNotAvailableException("unavailable", "serial");
+ throw new DeviceNotAvailableException(
+ "unavailable", "serial", DeviceErrorIdentifier.DEVICE_UNAVAILABLE);
}
if (mDeviceUnresponsive) {
- throw new DeviceUnresponsiveException("unresponsive", "serial");
+ throw new DeviceUnresponsiveException(
+ "unresponsive", "serial", DeviceErrorIdentifier.DEVICE_UNRESPONSIVE);
}
if (mThrowError && i == mNumTest / 2) {
throw new AssertionError("assert error");
@@ -238,7 +241,9 @@
continue;
}
listener.testStarted(test);
- listener.testFailed(test, "I failed.");
+ listener.testFailed(
+ test,
+ FailureDescription.create("I failed.", FailureStatus.TEST_FAILURE));
listener.testEnded(test, new HashMap<String, Metric>());
}
listener.testRunEnded(0, new HashMap<String, Metric>());
@@ -539,7 +544,7 @@
EasyMock.anyLong(),
EasyMock.<HashMap<String, Metric>>anyObject());
}
- mMockListener.testFailed(EasyMock.anyObject(), (String) EasyMock.anyObject());
+ mMockListener.testFailed(EasyMock.anyObject(), (FailureDescription) EasyMock.anyObject());
Capture<FailureDescription> captured = new Capture<>();
mMockListener.testRunFailed(EasyMock.capture(captured));
mMockListener.testRunEnded(
@@ -981,7 +986,7 @@
EasyMock.anyLong(),
EasyMock.<HashMap<String, Metric>>anyObject());
}
- mMockListener.testFailed(EasyMock.anyObject(), (String) EasyMock.anyObject());
+ mMockListener.testFailed(EasyMock.anyObject(), (FailureDescription) EasyMock.anyObject());
mMockListener.testRunFailed((FailureDescription) EasyMock.anyObject());
mMockListener.testRunEnded(
EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
@@ -1043,17 +1048,11 @@
EasyMock.anyLong(),
EasyMock.<HashMap<String, Metric>>anyObject());
}
- mMockListener.testFailed(EasyMock.anyObject(), (String) EasyMock.anyObject());
+ mMockListener.testFailed(EasyMock.anyObject(), (FailureDescription) EasyMock.anyObject());
mMockListener.testRunFailed((FailureDescription) EasyMock.anyObject());
mMockListener.testRunEnded(
EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
- // Run failed
- EasyMock.expect(mMockDevice.getIDevice()).andReturn(EasyMock.createMock(IDevice.class));
- EasyMock.expect(mMockDevice.getSerialNumber()).andReturn("serial");
- EasyMock.expect(mMockDevice.logBugreport(EasyMock.anyObject(), EasyMock.anyObject()))
- .andReturn(true);
-
replayMocks();
mModule.run(mModuleInfo, mMockListener);
// Only one module
@@ -1120,7 +1119,8 @@
throws DeviceNotAvailableException {
listener.testRunStarted("test", 1);
listener.testFailed(
- new TestDescription("failedclass", "failedmethod"), "trace");
+ new TestDescription("failedclass", "failedmethod"),
+ FailureDescription.create("trace", FailureStatus.TEST_FAILURE));
}
});
mTargetPrepList.clear();
@@ -1168,7 +1168,9 @@
TestDescription tid = new TestDescription("class", "method");
listener.testRunStarted("test", 1);
listener.testStarted(tid);
- listener.testFailed(tid, "I failed");
+ listener.testFailed(
+ tid,
+ FailureDescription.create("I failed", FailureStatus.TEST_FAILURE));
listener.testEnded(tid, new HashMap<String, Metric>());
listener.testRunEnded(0, new HashMap<String, Metric>());
}
@@ -1325,7 +1327,8 @@
public void run(TestInformation testInfo, ITestInvocationListener listener)
throws DeviceNotAvailableException {
listener.testFailed(
- new TestDescription("failedclass", "failedmethod"), "trace");
+ new TestDescription("failedclass", "failedmethod"),
+ FailureDescription.create("trace", FailureStatus.TEST_FAILURE));
}
});
mTargetPrepList.clear();
@@ -1387,8 +1390,9 @@
EasyMock.anyLong(),
EasyMock.<HashMap<String, Metric>>anyObject());
}
- mMockListener.testFailed(EasyMock.anyObject(), (String) EasyMock.anyObject());
- FailureDescription issues = FailureDescription.create("unresponsive");
+ mMockListener.testFailed(EasyMock.anyObject(), (FailureDescription) EasyMock.anyObject());
+ FailureDescription issues =
+ FailureDescription.create("unresponsive", FailureStatus.LOST_SYSTEM_UNDER_TEST);
mMockListener.testRunFailed(issues);
mMockListener.testRunEnded(
EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
@@ -1564,7 +1568,6 @@
mModule.setBuild(mMockBuildInfo);
mModule.setDevice(mMockDevice);
- EasyMock.expect(mMockDevice.getIDevice()).andReturn(new StubDevice("fake"));
EasyMock.expect(mMockPrep.isDisabled()).andReturn(false).times(2);
// no isTearDownDisabled() expected for setup
mMockPrep.setUp(EasyMock.eq(mModuleInfo));
@@ -1635,7 +1638,8 @@
EasyMock.<HashMap<String, Metric>>anyObject());
TestDescription testFail0 = new TestDescription(runName + "0class", "fail0");
mMockListener.testStarted(EasyMock.eq(testFail0), EasyMock.anyLong());
- mMockListener.testFailed(EasyMock.eq(testFail0), (String) EasyMock.anyObject());
+ mMockListener.testFailed(
+ EasyMock.eq(testFail0), (FailureDescription) EasyMock.anyObject());
mMockListener.testEnded(
EasyMock.eq(testFail0),
EasyMock.anyLong(),
@@ -1656,7 +1660,8 @@
EasyMock.<HashMap<String, Metric>>anyObject());
TestDescription testFail0_1 = new TestDescription(runName + "1class", "fail0");
mMockListener.testStarted(EasyMock.eq(testFail0_1), EasyMock.anyLong());
- mMockListener.testFailed(EasyMock.eq(testFail0_1), (String) EasyMock.anyObject());
+ mMockListener.testFailed(
+ EasyMock.eq(testFail0_1), (FailureDescription) EasyMock.anyObject());
mMockListener.testEnded(
EasyMock.eq(testFail0_1),
EasyMock.anyLong(),
@@ -1741,7 +1746,8 @@
}
TestDescription testFail0 = new TestDescription(runName + "0class", "fail0");
mMockListener.testStarted(EasyMock.eq(testFail0), EasyMock.anyLong());
- mMockListener.testFailed(EasyMock.eq(testFail0), (String) EasyMock.anyObject());
+ mMockListener.testFailed(
+ EasyMock.eq(testFail0), (FailureDescription) EasyMock.anyObject());
mMockListener.testEnded(
EasyMock.eq(testFail0),
EasyMock.anyLong(),
@@ -1766,7 +1772,8 @@
}
TestDescription testFail0_1 = new TestDescription(runName + "1class", "fail0");
mMockListener.testStarted(EasyMock.eq(testFail0_1), EasyMock.anyLong());
- mMockListener.testFailed(EasyMock.eq(testFail0_1), (String) EasyMock.anyObject());
+ mMockListener.testFailed(
+ EasyMock.eq(testFail0_1), (FailureDescription) EasyMock.anyObject());
mMockListener.testEnded(
EasyMock.eq(testFail0_1),
EasyMock.anyLong(),
diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java
index 2b3db6b..5d43bb2 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java
@@ -37,8 +37,11 @@
import org.junit.runners.JUnit4;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
/** Unit tests for {@link ModuleSplitter}. */
@RunWith(JUnit4.class)
@@ -46,6 +49,8 @@
private static final String DEFAULT_DEVICE = ConfigurationDef.DEFAULT_DEVICE_NAME;
private TestInformation mTestInfo = TestInformation.newBuilder().build();
+ private Map<String, List<ITargetPreparer>> mSuitePreparersPerDevice =
+ new HashMap<String, List<ITargetPreparer>>();
/**
* Tests that {@link ModuleSplitter#splitConfiguration(TestInformation, LinkedHashMap, int,
@@ -69,7 +74,8 @@
setter.setOptionValue("not-shardable", "true");
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true);
// matching 1 for 1, config to ModuleDefinition since not shardable
assertEquals(1, res.size());
// The original target preparer is changed since we split multiple <test> tags.
@@ -105,7 +111,8 @@
setter.setOptionValue("not-strict-shardable", "true");
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true);
// We are sharding since even if we are not-strict-shardable, we are in dynamic context
assertEquals(10, res.size());
// The original target preparer is changed since we split multiple <test> tags.
@@ -140,7 +147,8 @@
setter.setOptionValue("not-strict-shardable", "true");
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, false, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, false, true);
// matching 1 for 1, config to ModuleDefinition since not shardable
assertEquals(1, res.size());
// The original target preparer is changed since we split multiple <test> tags.
@@ -169,7 +177,8 @@
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, false);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, false);
// matching 1 for 1, config to ModuleDefinition since no intra-module sharding
assertEquals(1, res.size());
// The original target preparer is changed since we split multiple <test> tags.
@@ -206,7 +215,8 @@
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true);
// matching 1 for 1, config to ModuleDefinition since not shardable
assertEquals(1, res.size());
// The original target preparer is not there, it has been copied
@@ -239,7 +249,8 @@
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true);
// matching 1 for 1, config to ModuleDefinition since did not shard
assertEquals(1, res.size());
// The original target preparer is not there, it has been copied
@@ -271,14 +282,25 @@
setter.setOptionValue("num-shards", "6");
config.setTest(test);
+ Map<String, List<ITargetPreparer>> suitePreparers =
+ new HashMap<String, List<ITargetPreparer>>();
+ ITargetPreparer preparer1 = new StubTargetPreparer();
+ ITargetPreparer preparer2 = new StubTargetPreparer();
+ List<ITargetPreparer> preparers = Arrays.asList(preparer1, preparer2);
+ suitePreparers.put(DEFAULT_DEVICE, preparers);
+
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, suitePreparers, 5, true, true);
// matching 1 for 10 since tests sharding in 5 units times 2.
assertEquals(10, res.size());
// The original IRemoteTest does not exists anymore, new IRemoteTests have been created.
for (ModuleDefinition m : res) {
assertNotSame(test, m.getTests().get(0));
+ assertEquals(2, m.getSuitePreparerForDevice(DEFAULT_DEVICE).size());
+ assertNotSame(preparer1, m.getSuitePreparerForDevice(DEFAULT_DEVICE).get(0));
+ assertNotSame(preparer1, m.getSuitePreparerForDevice(DEFAULT_DEVICE).get(1));
}
assertTrue(config.getTests().isEmpty());
}
@@ -302,7 +324,8 @@
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, false, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, false, true);
// matching 1 for 6 since tests sharding in 6 tests.
assertEquals(6, res.size());
// The original IRemoteTest does not exists anymore, new IRemoteTests have been created.
diff --git a/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java b/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
index 07e0807..81fa44f 100644
--- a/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
@@ -20,6 +20,7 @@
import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.config.Configuration;
import com.android.tradefed.config.ConfigurationDef;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.ConfigurationFactory;
@@ -87,6 +88,7 @@
private IDeviceBuildInfo mBuildInfo;
private ITestDevice mMockDevice;
private TestInformation mTestInfo;
+ private IConfiguration mStubMainConfiguration;
private static final String TEST_MAINLINE_CONFIG =
"<configuration description=\"Runs a stub tests part of some suite\">\n"
@@ -115,6 +117,8 @@
mMainlineRunner = new FakeMainlineTMSR();
mMainlineRunner.setBuild(mBuildInfo);
mMainlineRunner.setDevice(mMockDevice);
+ mStubMainConfiguration = new Configuration("stub", "stub");
+ mMainlineRunner.setConfiguration(mStubMainConfiguration);
mMainlineOptionSetter = new OptionSetter(mMainlineRunner);
IInvocationContext context = new InvocationContext();
diff --git a/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java b/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java
index 11dee18..4a5d707 100644
--- a/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java
@@ -62,7 +62,7 @@
public void testApplySetup() {
EasyMock.expect(mMockBuildInfo.getBuildBranch()).andStubReturn("branch");
EasyMock.replay(mMockBuildInfo);
- mHandler = new MainlineModuleHandler("mod1.apk", mAbi, mContext);
+ mHandler = new MainlineModuleHandler("mod1.apk", mAbi, mContext, false);
mHandler.applySetup(mConfig);
assertTrue(mConfig.getTargetPreparers().get(0) instanceof InstallApexModuleTargetPreparer);
InstallApexModuleTargetPreparer preparer =
@@ -77,7 +77,7 @@
public void testApplySetup_MultipleMainlineModules() {
EasyMock.expect(mMockBuildInfo.getBuildBranch()).andStubReturn("branch");
EasyMock.replay(mMockBuildInfo);
- mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext);
+ mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext, false);
mHandler.applySetup(mConfig);
assertTrue(mConfig.getTargetPreparers().get(0) instanceof InstallApexModuleTargetPreparer);
InstallApexModuleTargetPreparer preparer =
@@ -97,7 +97,7 @@
try {
EasyMock.expect(mMockBuildInfo.getBuildBranch()).andStubReturn(null);
EasyMock.replay(mMockBuildInfo);
- mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext);
+ mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext, false);
fail("Should have thrown an exception.");
} catch (IllegalArgumentException expected) {
// expected
diff --git a/tests/src/com/android/tradefed/util/AaptParserTest.java b/tests/src/com/android/tradefed/util/AaptParserTest.java
index 65bf70d..93caecf 100644
--- a/tests/src/com/android/tradefed/util/AaptParserTest.java
+++ b/tests/src/com/android/tradefed/util/AaptParserTest.java
@@ -168,6 +168,34 @@
assertTrue(p.isRequestingLegacyStorage());
}
+ public void testParseXmlTreeForAapt2_withRequestLegacyFlagTrue() {
+ AaptParser p = new AaptParser();
+ p.parseXmlTree(
+ "N: android=http://schemas.android.com/apk/res/android\n"
+ + " E: manifest (line=2)\n"
+ + " A: http://schemas.android.com/apk/res/android:versionCode(0x0101021b)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/android:versionName(0x0101021c)=\"R\" (Raw: \"R\")\n"
+ + " A: http://schemas.android.com/apk/res/android:compileSdkVersion(0x01010572)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/android:compileSdkVersionCodename(0x01010573)=\"R\" (Raw: "
+ + "\"R\")\n"
+ + " A: package=\"com.android.foo\" (Raw: \"com.android.foo\")\n"
+ + " A: platformBuildVersionCode=(type 0x10)0x1d\n"
+ + " A: platformBuildVersionName=\"R\" (Raw: \"R\")\n"
+ + " E: uses-sdk (line=5)\n"
+ + " A: http://schemas.android.com/apk/res/android:minSdkVersion(0x0101020c)=(type 0x10)0x1c\n"
+ + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=\"R\" (Raw: \"R\")\n"
+ + " E: application (line=12)\n"
+ + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=(type 0x10)0x1e\n"
+ + " A: http://schemas.android.com/apk/res/android:supportsRtl(0x010103af)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/android:extractNativeLibs(0x010104ea)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/android:appComponentFactory(0x0101057a)=\"androidx.core.app"
+ + ".CoreComponentFactory\" (Raw: \"androidx.core.app"
+ + ".CoreComponentFactory\")\n"
+ + " A: http://schemas.android.com/apk/res/android:requestLegacyExternalStorage(0x01010603)=(type 0x12)"
+ + "0xffffffff\n");
+ assertTrue(p.isRequestingLegacyStorage());
+ }
+
public void testParseXmlTree_withRequestLegacyFlagFalse() {
AaptParser p = new AaptParser();
p.parseXmlTree(
@@ -196,6 +224,34 @@
assertFalse(p.isRequestingLegacyStorage());
}
+ public void testParseXmlTreeForAapt2_withRequestLegacyFlagFalse() {
+ AaptParser p = new AaptParser();
+ p.parseXmlTree(
+ "N: android=http://schemas.android.com/apk/res/android\n"
+ + " E: manifest (line=2)\n"
+ + " A: http://schemas.android.com/apk/res/android:versionCode(0x0101021b)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/android:versionName(0x0101021c)=\"R\" (Raw: \"R\")\n"
+ + " A: http://schemas.android.com/apk/res/android:compileSdkVersion(0x01010572)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/android:compileSdkVersionCodename(0x01010573)=\"R\" (Raw: "
+ + "\"R\")\n"
+ + " A: package=\"com.android.foo\" (Raw: \"com.android.foo\")\n"
+ + " A: platformBuildVersionCode=(type 0x10)0x1d\n"
+ + " A: platformBuildVersionName=\"R\" (Raw: \"R\")\n"
+ + " E: uses-sdk (line=5)\n"
+ + " A: http://schemas.android.com/apk/res/android:minSdkVersion(0x0101020c)=(type 0x10)0x1c\n"
+ + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=\"R\" (Raw: \"R\")\n"
+ + " E: application (line=12)\n"
+ + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=(type 0x10)0x1e\n"
+ + " A: http://schemas.android.com/apk/res/android:supportsRtl(0x010103af)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/android:extractNativeLibs(0x010104ea)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/android:appComponentFactory(0x0101057a)=\"androidx.core.app"
+ + ".CoreComponentFactory\" (Raw: \"androidx.core.app"
+ + ".CoreComponentFactory\")\n"
+ + " A: http://schemas.android.com/apk/res/android:requestLegacyExternalStorage(0x01010603)=(type 0x12)"
+ + "0x0\n");
+ assertFalse(p.isRequestingLegacyStorage());
+ }
+
public void testParseXmlTree_withoutRequestLegacyFlag() {
AaptParser p = new AaptParser();
p.parseXmlTree(
@@ -221,7 +277,32 @@
assertFalse(p.isRequestingLegacyStorage());
}
- public void testParseXmlTree_withUsesPermissionManageExternalStorage() {
+ public void testParseXmlTreeForAapt2_withoutRequestLegacyFlag() {
+ AaptParser p = new AaptParser();
+ p.parseXmlTree(
+ "N: android=http://schemas.android.com/apk/res/android\n"
+ + " E: manifest (line=2)\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:versionCode(0x0101021b)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:versionName(0x0101021c)=\"R\" (Raw: \"R\")\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:compileSdkVersion(0x01010572)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:compileSdkVersionCodename(0x01010573)=\"R\" (Raw: "
+ + "\"R\")\n"
+ + " A: package=\"com.android.foo\" (Raw: \"com.android.foo\")\n"
+ + " A: platformBuildVersionCode=(type 0x10)0x1d\n"
+ + " A: platformBuildVersionName=\"R\" (Raw: \"R\")\n"
+ + " E: uses-sdk (line=5)\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:minSdkVersion(0x0101020c)=(type 0x10)0x1c\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:targetSdkVersion(0x01010270)=\"R\" (Raw: \"R\")\n"
+ + " E: application (line=12)\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:targetSdkVersion(0x01010270)=(type 0x10)0x1e\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:supportsRtl(0x010103af)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:extractNativeLibs(0x010104ea)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:appComponentFactory(0x0101057a)=\"androidx.core.app"
+ + ".CoreComponentFactory\" (Raw: \"androidx.core.app");
+ assertFalse(p.isRequestingLegacyStorage());
+ }
+
+ public void testParse_withUsesPermissionManageExternalStorage() {
AaptParser p = new AaptParser();
p.parse(
"package: name='com.android.foo' versionCode='217173' versionName='1.7173' "
@@ -234,7 +315,7 @@
assertTrue(p.isUsingPermissionManageExternalStorage());
}
- public void testParseXmlTree_withoutUsesPermissionManageExternalStorage() {
+ public void testParse_withoutUsesPermissionManageExternalStorage() {
AaptParser p = new AaptParser();
p.parse(
"package: name='com.android.foo' versionCode='217173' versionName='1.7173' "
diff --git a/tests/src/com/android/tradefed/util/AdbRootElevatorTest.java b/tests/src/com/android/tradefed/util/AdbRootElevatorTest.java
new file mode 100644
index 0000000..2e808d3
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/AdbRootElevatorTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.INativeDevice;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link AdbRootElevator}. */
+@RunWith(JUnit4.class)
+public class AdbRootElevatorTest {
+ @Mock INativeDevice mMockDevice;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testEnablesAndDisablesRoot() throws Exception {
+ when(mMockDevice.isAdbRoot()).thenReturn(false);
+ when(mMockDevice.enableAdbRoot()).thenReturn(true);
+
+ try (AdbRootElevator adbRoot = new AdbRootElevator(mMockDevice)) {
+ mMockDevice.waitForDeviceAvailable();
+ }
+
+ InOrder inOrder = Mockito.inOrder(mMockDevice);
+ inOrder.verify(mMockDevice).isAdbRoot();
+ inOrder.verify(mMockDevice).enableAdbRoot();
+ inOrder.verify(mMockDevice).waitForDeviceAvailable();
+ inOrder.verify(mMockDevice).disableAdbRoot();
+ }
+
+ @Test
+ public void testRootAlreadyEnabled_doesNotEnableOrDisableRoot() throws Exception {
+ when(mMockDevice.isAdbRoot()).thenReturn(true);
+
+ try (AdbRootElevator adbRoot = new AdbRootElevator(mMockDevice)) {
+ mMockDevice.waitForDeviceAvailable();
+ }
+
+ InOrder inOrder = Mockito.inOrder(mMockDevice);
+ inOrder.verify(mMockDevice).isAdbRoot();
+ inOrder.verify(mMockDevice).waitForDeviceAvailable();
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testFailsToEnableAdbRoot_throwsException() throws Exception {
+ when(mMockDevice.isAdbRoot()).thenReturn(false);
+ when(mMockDevice.enableAdbRoot()).thenReturn(false);
+
+ try (AdbRootElevator adbRoot = new AdbRootElevator(mMockDevice)) {
+ fail("Exception should have already been thrown.");
+ } catch (Exception e) {
+ // Expected.
+ }
+
+ verify(mMockDevice, never()).disableAdbRoot();
+ }
+
+ @Test
+ public void testDeviceNotAvailableOnRoot_throwsException() throws Exception {
+ when(mMockDevice.isAdbRoot()).thenReturn(false);
+ when(mMockDevice.enableAdbRoot()).thenThrow(new DeviceNotAvailableException());
+
+ try (AdbRootElevator adbRoot = new AdbRootElevator(mMockDevice)) {
+ fail("Exception should have already been thrown.");
+ } catch (DeviceNotAvailableException e) {
+ // Expected.
+ }
+
+ verify(mMockDevice, never()).disableAdbRoot();
+ }
+}
diff --git a/tests/src/com/android/tradefed/util/ProtoUtilTest.java b/tests/src/com/android/tradefed/util/ProtoUtilTest.java
new file mode 100644
index 0000000..2e42eee
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/ProtoUtilTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import com.android.tradefed.util.test.ProtoUtilTestProto.TestMessage;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Unit tests for {@link ProtoUtil} */
+@RunWith(Parameterized.class)
+public class ProtoUtilTest {
+ @Parameter(0)
+ public String mTestName; // Unused, for identifying tests only.
+
+ @Parameter(1)
+ public TestMessage mMessage;
+
+ @Parameter(2)
+ public List<String> mReferences;
+
+ @Parameter(3)
+ public List<String> mExpectedResults;
+
+ @Parameters(name = "{0}#{index}")
+ public static Iterable<Object[]> data() {
+ List<Object[]> parameters = new ArrayList<>();
+ parameters.add(
+ new Object[] {
+ "returnsMessageAsStringForEmptyReference",
+ TestMessage.newBuilder().setIntField(7).build(),
+ new ArrayList<String>(),
+ Arrays.asList(TestMessage.newBuilder().setIntField(7).build().toString())
+ });
+ parameters.add(
+ new Object[] {
+ "singleLevel",
+ TestMessage.newBuilder().setIntField(7).build(),
+ Arrays.asList("int_field"),
+ Arrays.asList("7")
+ });
+ parameters.add(
+ new Object[] {
+ "singleLevel",
+ TestMessage.newBuilder().setStringField("string").build(),
+ Arrays.asList("string_field"),
+ Arrays.asList("string")
+ });
+ parameters.add(
+ new Object[] {
+ "singleLevel",
+ TestMessage.newBuilder()
+ .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7))
+ .build(),
+ Arrays.asList("message_field"),
+ Arrays.asList(TestMessage.SubMessage.newBuilder().setIntField(7).toString())
+ });
+ parameters.add(
+ new Object[] {
+ "singleLevelRepeated",
+ TestMessage.newBuilder()
+ .addAllRepeatedStringField(Arrays.asList("string1", "string2"))
+ .build(),
+ Arrays.asList("repeated_string_field"),
+ Arrays.asList("string1", "string2")
+ });
+ parameters.add(
+ new Object[] {
+ "multiLevel",
+ TestMessage.newBuilder()
+ .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7))
+ .build(),
+ Arrays.asList("message_field", "int_field"),
+ Arrays.asList("7")
+ });
+ parameters.add(
+ new Object[] {
+ "multiLevelRepeated",
+ TestMessage.newBuilder()
+ .setMessageField(
+ TestMessage.SubMessage.newBuilder()
+ .addAllRepeatedStringField(
+ Arrays.asList("string1", "string2")))
+ .build(),
+ Arrays.asList("message_field", "repeated_string_field"),
+ Arrays.asList("string1", "string2")
+ });
+ parameters.add(
+ new Object[] {
+ "multiLevelRepeated",
+ TestMessage.newBuilder()
+ .addAllRepeatedMessageField(
+ Arrays.asList(
+ TestMessage.SubMessage.newBuilder()
+ .addAllRepeatedStringField(
+ Arrays.asList("string1", "string2"))
+ .build(),
+ TestMessage.SubMessage.newBuilder()
+ .addAllRepeatedStringField(
+ Arrays.asList("string3", "string4"))
+ .build()))
+ .build(),
+ Arrays.asList("repeated_message_field", "repeated_string_field"),
+ Arrays.asList("string1", "string2", "string3", "string4")
+ });
+ parameters.add(
+ new Object[] {
+ "oneofSingleLevel",
+ TestMessage.newBuilder().setOneofStringField("string").build(),
+ Arrays.asList("oneof_string_field"),
+ Arrays.asList("string")
+ });
+ parameters.add(
+ new Object[] {
+ "oneofMultiLevel",
+ TestMessage.newBuilder()
+ .setOneofMessageField(
+ TestMessage.SubMessage.newBuilder()
+ .addAllRepeatedStringField(
+ Arrays.asList("string1", "string2")))
+ .build(),
+ Arrays.asList("oneof_message_field", "repeated_string_field"),
+ Arrays.asList("string1", "string2")
+ });
+ parameters.add(
+ new Object[] {
+ "returnsEmptyForNonExistentFieldReference",
+ TestMessage.newBuilder().setStringField("string").build(),
+ Arrays.asList("not_a_field"),
+ new ArrayList<String>()
+ });
+ parameters.add(
+ new Object[] {
+ "returnsEmptyForNonExistentFieldReference",
+ TestMessage.newBuilder()
+ .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7))
+ .build(),
+ Arrays.asList("message_field", "not_a_field"),
+ new ArrayList<String>()
+ });
+ parameters.add(
+ new Object[] {
+ "returnsEmptyForNonExistentFieldReference",
+ TestMessage.newBuilder()
+ .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7))
+ .build(),
+ Arrays.asList("message_field", "int_field", "not_a_field"),
+ new ArrayList<String>()
+ });
+ return parameters;
+ }
+
+ @Test
+ public void testParsing() {
+ assertArrayEquals(
+ mExpectedResults.toArray(),
+ ProtoUtil.getNestedFieldFromMessageAsStrings(mMessage, mReferences).toArray());
+ }
+}
diff --git a/tests/src/com/android/tradefed/util/PythonVirtualenvHelperTest.java b/tests/src/com/android/tradefed/util/PythonVirtualenvHelperTest.java
new file mode 100644
index 0000000..aeb1c24
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/PythonVirtualenvHelperTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+import com.google.common.base.Throwables;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.io.File;
+import java.nio.file.Paths;
+
+public class PythonVirtualenvHelperTest {
+
+ private File mVenvDir;
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtil.recursiveDelete(mVenvDir);
+ }
+
+ @Test
+ public void testActivate_shouldThrowNPE_whenVirtualenvPathIsNull() throws Exception {
+ String nullVirtualenvPath = null;
+ IRunUtil runUtil = mock(RunUtil.class);
+
+ try {
+ PythonVirtualenvHelper.activate(runUtil, nullVirtualenvPath);
+ fail("Should have thrown an exception");
+ } catch (NullPointerException e) {
+ assertThat(
+ String.format(
+ "An unexpected exception was thrown, full stack trace: %s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ containsString("Path to the Python virtual environment should not be null"));
+ }
+ }
+
+ @Test
+ public void testActivate_whenVirtualenvPathIsInvalid() throws Exception {
+ mVenvDir = FileUtil.createTempDir("venv");
+ mVenvDir.delete();
+ IRunUtil runUtil = mock(RunUtil.class);
+
+ try {
+ PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath());
+ fail("Should have thrown an exception");
+ } catch (RuntimeException e) {
+ assertThat(
+ String.format(
+ "An unexpected exception was thrown, full stack trace: %s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ containsString("Invalid python virtualenv path"));
+ }
+ }
+
+ @Test
+ public void testActivate_whenPythonBinNotFound() throws Exception {
+ mVenvDir = FileUtil.createTempDir("venv");
+ IRunUtil runUtil = mock(RunUtil.class);
+
+ try {
+ PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath());
+ fail("Should have thrown an exception");
+ } catch (RuntimeException e) {
+ assertThat(
+ String.format(
+ "An unexpected exception was thrown, full stack trace: %s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ containsString("Invalid python virtualenv path"));
+ }
+ }
+
+ @Test
+ public void testActivate_success() throws Exception {
+ mVenvDir = FileUtil.createTempDir("venv");
+ File pythonBin = new File(mVenvDir, "bin");
+ pythonBin.mkdir();
+ IRunUtil runUtil = mock(RunUtil.class);
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ result.setStdout(
+ "Name: pip\nLocation: "
+ + Paths.get(mVenvDir.getAbsolutePath(), "lib/python3.8/site-packages"));
+ when(runUtil.runTimedCmd(anyLong(), anyString(), eq("show"), eq("pip"))).thenReturn(result);
+
+ PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath());
+
+ verify(runUtil)
+ .setEnvVariable("PATH", pythonBin.getAbsolutePath() + ":" + System.getenv("PATH"));
+ verify(runUtil).setEnvVariable("VIRTUAL_ENV", mVenvDir.getAbsolutePath());
+ verify(runUtil)
+ .setEnvVariable(
+ "PYTHONPATH",
+ new File(mVenvDir, "lib/python3.8/site-packages").getAbsolutePath()
+ + ":"
+ + System.getenv("PYTHONPATH"));
+ verify(runUtil).unsetEnvVariable("PYTHONHOME");
+ }
+
+ @Test
+ public void testActivate_pipShowFails() throws Exception {
+ mVenvDir = FileUtil.createTempDir("venv");
+ File pythonBin = new File(mVenvDir, "bin");
+ pythonBin.mkdir();
+ IRunUtil runUtil = mock(RunUtil.class);
+ when(runUtil.runTimedCmd(anyLong(), anyString(), eq("show"), eq("pip")))
+ .thenReturn(new CommandResult());
+
+ try {
+ PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath());
+ fail("Should have thrown an exception");
+ } catch (RuntimeException e) {
+ assertThat(
+ String.format(
+ "An unexpected exception was thrown, full stack trace: %s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ containsString("pip3 show pip"));
+ }
+ }
+}
diff --git a/tests/src/com/android/tradefed/util/RemoteZipTest.java b/tests/src/com/android/tradefed/util/RemoteZipTest.java
index 0176a07..1d6056a 100644
--- a/tests/src/com/android/tradefed/util/RemoteZipTest.java
+++ b/tests/src/com/android/tradefed/util/RemoteZipTest.java
@@ -103,7 +103,7 @@
List<CentralDirectoryInfo> entries = remoteZip.getZipEntries();
- assertEquals(7, entries.size());
+ assertEquals(8, entries.size());
assertTrue(mExpectedEntries.containsAll(entries));
} finally {
FileUtil.recursiveDelete(destDir);
@@ -122,7 +122,7 @@
destDir = FileUtil.createTempDir("test");
RemoteZip remoteZip = new RemoteZip(REMOTE_FILE, mZipFileSize, mDownloader, true);
List<CentralDirectoryInfo> entries = remoteZip.getZipEntries();
- assertEquals(7, entries.size());
+ assertEquals(8, entries.size());
assertTrue(mExpectedEntries.containsAll(entries));
} finally {
FileUtil.recursiveDelete(destDir);
@@ -151,7 +151,7 @@
targetFile = Paths.get(destDir.getPath(), "executable", "executable_file").toFile();
assertTrue(targetFile.exists());
// File not in the list is not unzipped.
- targetFile = Paths.get(destDir.getPath(), "empty_file").toFile();
+ targetFile = Paths.get(destDir.getPath(), "empty/empty_file").toFile();
assertFalse(targetFile.exists());
} finally {
FileUtil.recursiveDelete(destDir);
diff --git a/tests/src/com/android/tradefed/util/SparseImageUtilTest.java b/tests/src/com/android/tradefed/util/SparseImageUtilTest.java
new file mode 100644
index 0000000..d8858e7
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/SparseImageUtilTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/** Unit tests for {@link SparseImageUtil} */
+@RunWith(JUnit4.class)
+public class SparseImageUtilTest {
+ private File mSparseImageFile;
+
+ @Before
+ public void setUp() throws IOException {
+ mSparseImageFile = FileUtil.createTempFile("sparse", ".img");
+ try (FileOutputStream out = new FileOutputStream(mSparseImageFile)) {
+ out.write(getSparseImageData());
+ }
+ }
+
+ @After
+ public void tearDown() {
+ FileUtil.deleteFile(mSparseImageFile);
+ }
+
+ /** Verify {@link com.android.tradefed.util.SparseImageUtil#isSparse}. */
+ @Test
+ public void testIsSparse() {
+ Assert.assertTrue(SparseImageUtil.isSparse(mSparseImageFile));
+ }
+
+ /** Verify {@link com.android.tradefed.util.SparseImageUtil#unsparse}. */
+ @Test
+ public void testUnsparse() throws IOException {
+ File unsparsedFile = FileUtil.createTempFile("unsparse", ".img");
+ byte[] unsparsedData = null;
+ try {
+ SparseImageUtil.unsparse(mSparseImageFile, unsparsedFile);
+ try (FileInputStream in = new FileInputStream(unsparsedFile)) {
+ unsparsedData = StreamUtil.getByteArrayListFromStream(in).getContents();
+ }
+ Assert.assertArrayEquals(getUnsparsedImageData(), unsparsedData);
+ } finally {
+ FileUtil.deleteFile(unsparsedFile);
+ }
+ }
+
+ /**
+ * Returns some sparse data.
+ *
+ * @see https://android.googlesource.com/platform/system/core/+/master/libsparse/sparse_format.h
+ */
+ private byte[] getSparseImageData() {
+ final int SPARSE_IMAGE_MAGIC = 0xED26FF3A;
+ ByteBuffer buffer = ByteBuffer.allocate(4096);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ // Header
+ buffer.putInt(SPARSE_IMAGE_MAGIC);
+ buffer.putShort((short) 1);
+ buffer.putShort((short) 0);
+ buffer.putShort((short) 28);
+ buffer.putShort((short) 12);
+ buffer.putInt(4); /* block size */
+ buffer.putInt(512 + 256); /* total blocks */
+ buffer.putInt(2); /* total chunks */
+ buffer.putInt(0); /* ignore check sum */
+ // RAW chunk, 2048 bytes of lorem ipsum
+ byte[] loremIpsum = getLoremIpsum();
+ buffer.putShort((short) 0xCAC1);
+ buffer.putShort((short) 0); /* padding */
+ buffer.putInt(loremIpsum.length / 4); /* data size in terms of number of blocks */
+ buffer.putInt(12 + loremIpsum.length); /* header size + data size */
+ buffer.put(loremIpsum);
+ // DONTCARE chunk, 1024 bytes of zeroes
+ byte[] zeroes = new byte[1024];
+ buffer.putShort((short) 0xCAC3);
+ buffer.putShort((short) 0); /* padding */
+ buffer.putInt(zeroes.length / 4); /* data size in terms of number of blocks */
+ buffer.putInt(12 + zeroes.length); /* header size + data size */
+ buffer.put(zeroes);
+ return Arrays.copyOf(buffer.array(), buffer.position());
+ }
+
+ private byte[] getUnsparsedImageData() {
+ byte[] loremIpsum = getLoremIpsum();
+ // Pad lorem ipsum with 1024 bytes of zeroes
+ return Arrays.copyOf(loremIpsum, loremIpsum.length + 1024);
+ }
+
+ /** Returns a chunk of text data. */
+ private byte[] getLoremIpsum() {
+ final int dataLen = 2048; /* Must be a multiple of 4 */
+ final String loremIpsumString =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor"
+ + " incididunt ut labore et dolore magna aliqua. Enim neque volutpat ac"
+ + " tincidunt vitae semper quis lectus. Est pellentesque elit ullamcorper"
+ + " dignissim cras tincidunt lobortis feugiat vivamus. Vitae ultricies leo"
+ + " integer malesuada nunc vel. Ultrices tincidunt arcu non sodales neque"
+ + " sodales ut etiam sit. Arcu cursus vitae congue mauris rhoncus aenean."
+ + " Consectetur a erat nam at lectus urna duis convallis convallis. Suscipit"
+ + " tellus mauris a diam maecenas sed. At elementum eu facilisis sed odio."
+ + " Neque sodales ut etiam sit.";
+ final byte[] loremIpsumBytes = loremIpsumString.getBytes();
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ while (buffer.size() < dataLen) {
+ buffer.write(loremIpsumBytes, 0, loremIpsumBytes.length);
+ }
+ return Arrays.copyOf(buffer.toByteArray(), dataLen);
+ }
+}
diff --git a/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java b/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java
index 7a81c5e..f166b6a 100644
--- a/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java
+++ b/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java
@@ -88,4 +88,30 @@
assertArrayEquals(new String[]{"foo", "bar bar"},
StringEscapeUtils.paramsToArgs(expected).toArray());
}
+
+ /**
+ * Simple test that {@link StringEscapeUtils#escapeShell(String)} escapes the is greater than
+ * sign.
+ */
+ @Test
+ public void testEscapesGreaterSigns() {
+ String escaped_str = StringEscapeUtils.escapeShell(">greater>signs");
+ assertEquals("\\>greater\\>signs", escaped_str);
+ }
+
+ /**
+ * Simple test that {@link StringEscapeUtils#escapeShell(String)} escapes the is less than sign.
+ */
+ @Test
+ public void testEscapesLessSigns() {
+ String escaped_str = StringEscapeUtils.escapeShell("<less<signs");
+ assertEquals("\\<less\\<signs", escaped_str);
+ }
+
+ /** Simple test that {@link StringEscapeUtils#escapeShell(String)} escapes the or sign. */
+ @Test
+ public void testEscapesOrSigns() {
+ String escaped_str = StringEscapeUtils.escapeShell("|or|signs");
+ assertEquals("\\|or\\|signs", escaped_str);
+ }
}
diff --git a/tests/src/com/android/tradefed/util/ZipUtilTest.java b/tests/src/com/android/tradefed/util/ZipUtilTest.java
index 33841a0..cabf7fa 100644
--- a/tests/src/com/android/tradefed/util/ZipUtilTest.java
+++ b/tests/src/com/android/tradefed/util/ZipUtilTest.java
@@ -216,8 +216,8 @@
partialZipFile,
endCentralDirInfo,
endCentralDirInfo.getCentralDirOffset());
- // The zip file has 3 folders, 4 files.
- assertEquals(7, zipEntries.size());
+ // The zip file has 4 folders, 4 files.
+ assertEquals(8, zipEntries.size());
CentralDirectoryInfo zipEntry;
LocalFileHeader localFileHeader;
@@ -228,7 +228,7 @@
zipEntry =
zipEntries
.stream()
- .filter(e -> e.getFileName().equals("empty_file"))
+ .filter(e -> e.getFileName().equals("empty/empty_file"))
.findFirst()
.get();
targetFile = new File(Paths.get(tmpDir.toString(), zipEntry.getFileName()).toString());
@@ -243,6 +243,7 @@
// Verify file permissions - readonly - 644 rw-r--r--
permissions = Files.getPosixFilePermissions(targetFile.toPath());
assertEquals(PosixFilePermissions.fromString("rw-r--r--"), permissions);
+ assertTrue(targetFile.isFile());
// Unzip text file
zipEntry =
@@ -377,8 +378,8 @@
endCentralDirInfo,
endCentralDirInfo.getCentralDirOffset(),
true);
- // The zip file has 3 folders, 4 files.
- assertEquals(7, zipEntries.size());
+ // The zip file has 4 folders, 4 files.
+ assertEquals(8, zipEntries.size());
CentralDirectoryInfo zipEntry;
LocalFileHeader localFileHeader;
@@ -389,7 +390,7 @@
zipEntry =
zipEntries
.stream()
- .filter(e -> e.getFileName().equals("empty_file"))
+ .filter(e -> e.getFileName().equals("empty/empty_file"))
.findFirst()
.get();
targetFile = new File(Paths.get(tmpDir.toString(), zipEntry.getFileName()).toString());
diff --git a/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java b/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java
index e27ed0e..b3ba079 100644
--- a/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java
+++ b/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java
@@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
/** Unit tests for {@link ParallelDeviceExecutor}. */
@@ -49,7 +50,7 @@
mDevice2 = Mockito.mock(ITestDevice.class);
mDevices.add(mDevice1);
mDevices.add(mDevice2);
- mExecutor = new ParallelDeviceExecutor<>(mDevices);
+ mExecutor = new ParallelDeviceExecutor<>(mDevices.size());
}
@Test
@@ -93,4 +94,23 @@
assertTrue(mExecutor.getErrors().get(0).getMessage().contains("one"));
assertTrue(mExecutor.getErrors().get(1).getMessage().contains("two"));
}
+
+ @Test
+ public void testExecution_timeout() {
+ List<Callable<Boolean>> callableTasks = new ArrayList<>();
+ for (ITestDevice device : mDevices) {
+ callableTasks.add(
+ () -> {
+ Thread.sleep(1000L);
+ return true;
+ });
+ }
+
+ List<Boolean> results = mExecutor.invokeAll(callableTasks, 1L, TimeUnit.MILLISECONDS);
+ assertEquals(0, results.size());
+ assertTrue(mExecutor.hasErrors());
+ assertEquals(2, mExecutor.getErrors().size());
+ assertTrue(mExecutor.getErrors().get(0) instanceof CancellationException);
+ assertTrue(mExecutor.getErrors().get(1) instanceof CancellationException);
+ }
}
diff --git a/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java b/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java
index dd43440..f45238a 100644
--- a/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java
+++ b/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java
@@ -176,12 +176,14 @@
any(CollectingByteOutputReceiver.class));
List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID);
// Resulting list should have two metrics.
- assertThat(data.size()).comparesEqualTo(2);
+ assertThat(data.size()).isEquivalentAccordingToCompareTo(2);
// The first metric should correspond to METRIC_2_* as its timestamp is earlier.
- assertThat(data.get(0).getElapsedTimestampNanos()).comparesEqualTo(METRIC_2_NANOS);
+ assertThat(data.get(0).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_2_NANOS);
assertThat(data.get(0).getAtom().hasBleScanResultReceived()).isTrue();
// The second metric should correspond to METRIC_1_*.
- assertThat(data.get(1).getElapsedTimestampNanos()).comparesEqualTo(METRIC_1_NANOS);
+ assertThat(data.get(1).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_1_NANOS);
assertThat(data.get(1).getAtom().hasBleScanStateChanged()).isTrue();
}
@@ -204,7 +206,7 @@
any(CollectingByteOutputReceiver.class));
List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID);
// Resulting list should be empty.
- assertThat(data.size()).comparesEqualTo(0);
+ assertThat(data.size()).isEquivalentAccordingToCompareTo(0);
}
/**
@@ -230,12 +232,14 @@
any(CollectingByteOutputReceiver.class));
List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID);
// Resulting list should have two metrics.
- assertThat(data.size()).comparesEqualTo(2);
+ assertThat(data.size()).isEquivalentAccordingToCompareTo(2);
// The first metric should correspond to METRIC_2_* as its timestamp is earlier.
- assertThat(data.get(0).getElapsedTimestampNanos()).comparesEqualTo(METRIC_2_NANOS);
+ assertThat(data.get(0).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_2_NANOS);
assertThat(data.get(0).getAtom().hasBleScanResultReceived()).isTrue();
// The second metric should correspond to METRIC_1_*.
- assertThat(data.get(1).getElapsedTimestampNanos()).comparesEqualTo(METRIC_1_NANOS);
+ assertThat(data.get(1).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_1_NANOS);
assertThat(data.get(1).getAtom().hasBleScanStateChanged()).isTrue();
}
@@ -261,12 +265,14 @@
any(CollectingByteOutputReceiver.class));
List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID);
// Resulting list should have two metrics.
- assertThat(data.size()).comparesEqualTo(2);
+ assertThat(data.size()).isEquivalentAccordingToCompareTo(2);
// The first metric should correspond to METRIC_1_* as its timestamp is earlier.
- assertThat(data.get(0).getElapsedTimestampNanos()).comparesEqualTo(METRIC_1_NANOS);
+ assertThat(data.get(0).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_1_NANOS);
assertThat(data.get(0).getAtom().hasBleScanStateChanged()).isTrue();
// The second metric should correspond to METRIC_3_*.
- assertThat(data.get(1).getElapsedTimestampNanos()).comparesEqualTo(METRIC_3_NANOS);
+ assertThat(data.get(1).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_3_NANOS);
assertThat(data.get(1).getAtom().hasBleScanStateChanged()).isTrue();
}
diff --git a/util-apps/ContentProvider/main/AndroidManifest.xml b/util-apps/ContentProvider/main/AndroidManifest.xml
index ac37e68..15cb3fa 100644
--- a/util-apps/ContentProvider/main/AndroidManifest.xml
+++ b/util-apps/ContentProvider/main/AndroidManifest.xml
@@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<application>
<provider
diff --git a/util-apps/DeviceSetupUtil/src/com/android/tradefed/utils/MainActivity.java b/util-apps/DeviceSetupUtil/src/com/android/tradefed/utils/MainActivity.java
index 2ecd90d..80a0352 100644
--- a/util-apps/DeviceSetupUtil/src/com/android/tradefed/utils/MainActivity.java
+++ b/util-apps/DeviceSetupUtil/src/com/android/tradefed/utils/MainActivity.java
@@ -20,7 +20,7 @@
import android.os.Bundle;
/**
- * Dummy activity used to launch app for first time, so in future it can handle Dismiss Keyguard
+ * An activity used to launch app for first time, so in future it can handle Dismiss Keyguard
* intents on ICS and above devices.
*/
public class MainActivity extends Activity {