Merge tag 'android-security-10.0.0_r53' into int/10/fp2
Android security 10.0.0 release 53
* tag 'android-security-10.0.0_r53':
Merge "Ensure we take into account abi in exclude-filter" am: 16606dd95d am: be09346cba am: 909dd44e0f am: 56390dc187
Change-Id: I5a7867d69018c80c340000ddb47382b872af408d
diff --git a/atest/TEST_MAPPING b/atest/TEST_MAPPING
index 0769294..bcb8a43 100644
--- a/atest/TEST_MAPPING
+++ b/atest/TEST_MAPPING
@@ -1,4 +1,30 @@
{
+ "mainline-presubmit": [
+ {
+ "name": "HelloWorldTests"
+ },
+ {
+ "name": "hello_world_test",
+ "host": true
+ },
+ {
+ "name": "CtsApacheHttpLegacy27ApiSignatureTestCases"
+ },
+ {
+ "name": "ziparchive-tests"
+ },
+ {
+ "name": "CtsDpiTestCases",
+ "options": [
+ {
+ "include-filter": "android.dpi.cts.ConfigurationScreenLayoutTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsSampleHostTestCases"
+ }
+ ],
"presubmit": [
{
"name": "atest_unittests",
diff --git a/atest/atest_utils.py b/atest/atest_utils.py
index 9e03e8e..8b2399f 100644
--- a/atest/atest_utils.py
+++ b/atest/atest_utils.py
@@ -174,9 +174,19 @@
return False
-def get_result_server_args():
- """Return list of args for communication with result server."""
+def get_result_server_args(for_test_mapping=False):
+ """Return list of args for communication with result server.
+
+ Args:
+ for_test_mapping: True if the test run is for Test Mapping to include
+ additional reporting args. Default is False.
+ """
+ # TODO (b/147644460) Temporarily disable Sponge V1 since it will be turned
+ # down.
if _can_upload_to_result_server():
+ if for_test_mapping:
+ return (constants.RESULT_SERVER_ARGS +
+ constants.TEST_MAPPING_RESULT_SERVER_ARGS)
return constants.RESULT_SERVER_ARGS
return []
diff --git a/atest/test_runners/atest_tf_test_runner.py b/atest/test_runners/atest_tf_test_runner.py
index a4f62d5..c895b36 100644
--- a/atest/test_runners/atest_tf_test_runner.py
+++ b/atest/test_runners/atest_tf_test_runner.py
@@ -388,7 +388,10 @@
logging.info('%s does not support the following args %s',
self.EXECUTABLE, args_not_supported)
- test_args.extend(atest_utils.get_result_server_args())
+ # Only need to check one TestInfo to determine if the tests are
+ # configured in TEST_MAPPING.
+ for_test_mapping = test_infos and test_infos[0].from_test_mapping
+ test_args.extend(atest_utils.get_result_server_args(for_test_mapping))
self.run_cmd_dict['args'] = ' '.join(test_args)
return [self._RUN_CMD.format(**self.run_cmd_dict)]
@@ -495,10 +498,6 @@
if not test_infos:
return []
- # Only need to check one TestInfo to determine if the tests are
- # configured in TEST_MAPPING.
- if test_infos[0].from_test_mapping:
- args.extend(constants.TEST_MAPPING_RESULT_SERVER_ARGS)
test_infos = self._flatten_test_infos(test_infos)
for info in test_infos:
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
new file mode 100644
index 0000000..c551496
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/CertificationSuiteResultReporter.java
@@ -0,0 +1,597 @@
+/*
+ * 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
new file mode 100644
index 0000000..295692b
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopier.java
@@ -0,0 +1,138 @@
+/*
+ * 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
new file mode 100644
index 0000000..805a2ad
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
@@ -0,0 +1,622 @@
+/*
+ * 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
new file mode 100644
index 0000000..a0c3131
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/suite/PreviousSessionFileCopierTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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/src/com/android/tradefed/config/Configuration.java b/src/com/android/tradefed/config/Configuration.java
index 9982d59..280b0a7 100644
--- a/src/com/android/tradefed/config/Configuration.java
+++ b/src/com/android/tradefed/config/Configuration.java
@@ -41,6 +41,7 @@
import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.StubTest;
+import com.android.tradefed.testtype.coverage.CoverageOptions;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.QuotationAwareTokenizer;
@@ -98,6 +99,7 @@
public static final String METRIC_POST_PROCESSOR_TYPE_NAME = "metric_post_processor";
public static final String SANDBOX_TYPE_NAME = "sandbox";
public static final String SANBOX_OPTIONS_TYPE_NAME = "sandbox_options";
+ public static final String COVERAGE_OPTIONS_TYPE_NAME = "coverage";
private static Map<String, ObjTypeInfo> sObjTypeMap = null;
private static Set<String> sMultiDeviceSupportedTag = null;
@@ -185,6 +187,8 @@
METRIC_POST_PROCESSOR_TYPE_NAME,
new ObjTypeInfo(BasePostProcessor.class, true));
sObjTypeMap.put(SANBOX_OPTIONS_TYPE_NAME, new ObjTypeInfo(SandboxOptions.class, false));
+ sObjTypeMap.put(
+ COVERAGE_OPTIONS_TYPE_NAME, new ObjTypeInfo(CoverageOptions.class, false));
}
return sObjTypeMap;
}
@@ -239,6 +243,7 @@
setConfigurationDescriptor(new ConfigurationDescriptor());
setDeviceMetricCollectors(new ArrayList<>());
setPostProcessors(new ArrayList<>());
+ setCoverageOptions(new CoverageOptions());
setConfigurationObjectNoThrow(SANBOX_OPTIONS_TYPE_NAME, new SandboxOptions());
}
@@ -472,6 +477,13 @@
return (List<IDeviceConfiguration>)getConfigurationObjectList(DEVICE_NAME);
}
+ /** {@inheritDoc} */
+ @SuppressWarnings("unchecked")
+ @Override
+ public CoverageOptions getCoverageOptions() {
+ return (CoverageOptions) getConfigurationObject(COVERAGE_OPTIONS_TYPE_NAME);
+ }
+
/**
* {@inheritDoc}
*/
@@ -763,6 +775,12 @@
setConfigurationObjectListNoThrow(DEVICE_NAME, deviceConfigs);
}
+ /** {@inheritDoc} */
+ @Override
+ public void setCoverageOptions(CoverageOptions coverageOptions) {
+ setConfigurationObjectNoThrow(COVERAGE_OPTIONS_TYPE_NAME, coverageOptions);
+ }
+
/**
* {@inheritDoc}
*/
@@ -1490,6 +1508,12 @@
getConfigurationObject(SANBOX_OPTIONS_TYPE_NAME),
excludeFilters,
printDeprecatedOptions);
+ ConfigurationUtil.dumpClassToXml(
+ serializer,
+ COVERAGE_OPTIONS_TYPE_NAME,
+ getCoverageOptions(),
+ excludeFilters,
+ printDeprecatedOptions);
serializer.endTag(null, ConfigurationUtil.CONFIGURATION_NAME);
serializer.endDocument();
diff --git a/src/com/android/tradefed/config/IConfiguration.java b/src/com/android/tradefed/config/IConfiguration.java
index bc8d1dd..604b7f6 100644
--- a/src/com/android/tradefed/config/IConfiguration.java
+++ b/src/com/android/tradefed/config/IConfiguration.java
@@ -32,6 +32,7 @@
import com.android.tradefed.targetprep.ITargetPreparer;
import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.coverage.CoverageOptions;
import com.android.tradefed.util.keystore.IKeyStoreClient;
import org.json.JSONArray;
@@ -163,6 +164,13 @@
public IDeviceSelection getDeviceRequirements();
/**
+ * Gets the {@link CoverageOptions} to use from the configuration.
+ *
+ * @return the {@link CoverageOptions} provided in the configuration.
+ */
+ public CoverageOptions getCoverageOptions();
+
+ /**
* Generic interface to get the configuration object with the given type name.
*
* @param typeName the unique type of the configuration object
@@ -421,6 +429,9 @@
*/
public void setDeviceOptions(TestDeviceOptions deviceOptions);
+ /** Set the {@link CoverageOptions}, replacing any existing values. */
+ public void setCoverageOptions(CoverageOptions coverageOptions);
+
/**
* Generic method to set the config object with the given name, replacing any existing value.
*
diff --git a/src/com/android/tradefed/device/INativeDevice.java b/src/com/android/tradefed/device/INativeDevice.java
index 99a1b04..50be15e 100644
--- a/src/com/android/tradefed/device/INativeDevice.java
+++ b/src/com/android/tradefed/device/INativeDevice.java
@@ -1184,6 +1184,13 @@
public void remountSystemWritable() throws DeviceNotAvailableException;
/**
+ * Make the vendor partition on the device writable. May reboot the device.
+ *
+ * @throws DeviceNotAvailableException
+ */
+ public void remountVendorWritable() throws DeviceNotAvailableException;
+
+ /**
* Returns the key type used to sign the device image
* <p>
* Typically Android devices may be signed with test-keys (like in AOSP) or release-keys
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index 377abc1..c129357 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -3915,6 +3915,20 @@
waitForDeviceAvailable();
}
+ /** {@inheritDoc} */
+ @Override
+ public void remountVendorWritable() throws DeviceNotAvailableException {
+ String verity = getProperty("partition.vendor.verified");
+ // have the property set (regardless state) implies verity is enabled, so we send adb
+ // command to disable verity
+ if (verity != null && !verity.isEmpty()) {
+ executeAdbCommand("disable-verity");
+ reboot();
+ }
+ executeAdbCommand("remount");
+ waitForDeviceAvailable();
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java
index c82a8bd..eb06d2e 100644
--- a/src/com/android/tradefed/device/TestDevice.java
+++ b/src/com/android/tradefed/device/TestDevice.java
@@ -1401,9 +1401,12 @@
*/
@Override
public boolean hasFeature(String feature) throws DeviceNotAvailableException {
- final String output = executeShellCommand("pm list features");
- if (output.contains(feature)) {
- return true;
+ String commandOutput = executeShellCommand("pm list features");
+ for (String line: commandOutput.split("\\s+")) {
+ // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
+ if (feature.equals(line)) {
+ return true;
+ }
}
CLog.w("Feature: %s is not available on %s", feature, getSerialNumber());
return false;
diff --git a/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java b/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
index 1e4d16c..10885cf 100644
--- a/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
+++ b/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
@@ -130,6 +130,15 @@
// Does nothing
}
+ @Override
+ public void onTestEnd(
+ DeviceMetricData testData,
+ final Map<String, Metric> currentTestCaseMetrics,
+ TestDescription test) {
+ // Call the default implementation of onTestEnd if not overridden
+ onTestEnd(testData, currentTestCaseMetrics);
+ }
+
/** =================================== */
/** Invocation Listeners for forwarding */
@Override
@@ -233,7 +242,7 @@
TestDescription test, long endTime, HashMap<String, Metric> testMetrics) {
if (!mSkipTestCase) {
try {
- onTestEnd(mTestData, testMetrics);
+ onTestEnd(mTestData, testMetrics, test);
mTestData.addToMetrics(testMetrics);
} catch (Throwable t) {
// Prevent exception from messing up the status reporting.
diff --git a/src/com/android/tradefed/device/metric/IMetricCollector.java b/src/com/android/tradefed/device/metric/IMetricCollector.java
index 387e536..1c8b84f 100644
--- a/src/com/android/tradefed/device/metric/IMetricCollector.java
+++ b/src/com/android/tradefed/device/metric/IMetricCollector.java
@@ -113,4 +113,18 @@
*/
public void onTestEnd(
DeviceMetricData testData, final Map<String, Metric> currentTestCaseMetrics);
+
+ /**
+ * Callback when a test case is ended. This should be the time for clean up.
+ *
+ * @param testData the {@link DeviceMetricData} holding the data for the test case. Will be the
+ * same object as during {@link #onTestStart(DeviceMetricData)}.
+ * @param currentTestCaseMetrics the current map of metrics passed to {@link
+ * #testEnded(TestDescription, Map)}.
+ * @param test the {@link TestDescription} of the test case in progress.
+ */
+ public void onTestEnd(
+ DeviceMetricData testData,
+ final Map<String, Metric> currentTestCaseMetrics,
+ TestDescription test);
}
diff --git a/src/com/android/tradefed/host/HostOptions.java b/src/com/android/tradefed/host/HostOptions.java
index 8820eff..cb2f946 100644
--- a/src/com/android/tradefed/host/HostOptions.java
+++ b/src/com/android/tradefed/host/HostOptions.java
@@ -65,6 +65,11 @@
)
private Map<String, File> mJsonServiceAccountMap = new HashMap<>();
+ @Option(
+ name = "use-zip64-in-partial-download",
+ description = "Whether to use zip64 format in partial download.")
+ private boolean mUseZip64InPartialDownload = false;
+
/**
* {@inheritDoc}
*/
@@ -108,4 +113,10 @@
public void validateOptions() throws ConfigurationException {
// Validation of host options
}
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getUseZip64InPartialDownload() {
+ return mUseZip64InPartialDownload;
+ }
}
diff --git a/src/com/android/tradefed/host/IHostOptions.java b/src/com/android/tradefed/host/IHostOptions.java
index eb36dda..9c15151 100644
--- a/src/com/android/tradefed/host/IHostOptions.java
+++ b/src/com/android/tradefed/host/IHostOptions.java
@@ -56,4 +56,7 @@
/** Validate that the options set on {@link IHostOptions} are valid. */
void validateOptions() throws ConfigurationException;
+
+ /** Check if it should use the zip64 format in partial download or not. */
+ boolean getUseZip64InPartialDownload();
}
diff --git a/src/com/android/tradefed/result/ATestFileSystemLogSaver.java b/src/com/android/tradefed/result/ATestFileSystemLogSaver.java
new file mode 100644
index 0000000..eec9a0e
--- /dev/null
+++ b/src/com/android/tradefed/result/ATestFileSystemLogSaver.java
@@ -0,0 +1,42 @@
+/*
+ * 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.result;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+import java.io.IOException;
+
+/** This LogSaver class is used by ATest to save logs in a specific path. */
+@OptionClass(alias = "atest-file-system-log-saver")
+public class ATestFileSystemLogSaver extends FileSystemLogSaver {
+
+ @Option(name = "atest-log-file-path", description = "root file system path to store log files.")
+ private File mAtestRootReportDir = new File(System.getProperty("java.io.tmpdir"));
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return The directory created in the path that option atest-log-file-path specify.
+ */
+ @Override
+ protected File generateLogReportDir(IBuildInfo buildInfo, File reportDir) throws IOException {
+ return FileUtil.createTempDir("invocation_", mAtestRootReportDir);
+ }
+}
diff --git a/src/com/android/tradefed/result/FileSystemLogSaver.java b/src/com/android/tradefed/result/FileSystemLogSaver.java
index 35b83e6..f8ba5f0 100644
--- a/src/com/android/tradefed/result/FileSystemLogSaver.java
+++ b/src/com/android/tradefed/result/FileSystemLogSaver.java
@@ -195,8 +195,7 @@
File logReportDir;
// now create unique directory within the buildDir
try {
- File buildDir = createBuildDir(buildInfo, reportDir);
- logReportDir = FileUtil.createTempDir("inv_", buildDir);
+ logReportDir = generateLogReportDir(buildInfo, reportDir);
} catch (IOException e) {
CLog.e("Unable to create unique directory in %s. Attempting to use tmp dir instead",
reportDir.getAbsolutePath());
@@ -218,6 +217,18 @@
}
/**
+ * An exposed method that allow subclass to customize generating path logic.
+ *
+ * @param buildInfo the {@link IBuildInfo}
+ * @param reportDir the {@link File} for the report directory.
+ * @return The directory created.
+ */
+ protected File generateLogReportDir(IBuildInfo buildInfo, File reportDir) throws IOException {
+ File buildDir = createBuildDir(buildInfo, reportDir);
+ return FileUtil.createTempDir("inv_", buildDir);
+ }
+
+ /**
* A helper method to get or create a build directory based on the build info of the invocation.
* <p>
* Create a unique file system directory with the structure
diff --git a/src/com/android/tradefed/result/LogDataType.java b/src/com/android/tradefed/result/LogDataType.java
index 9589c90..2024bf1 100644
--- a/src/com/android/tradefed/result/LogDataType.java
+++ b/src/com/android/tradefed/result/LogDataType.java
@@ -27,6 +27,8 @@
MP4("mp4", "video/mp4", true, false),
EAR("ear", "application/octet-stream", true, false),
ZIP("zip", "application/zip", true, false),
+ SEVEN_Z("7z", "application/x-7z-compressed", true, false),
+ BITS("bits", "application/octet-stream", true, false),
JPEG("jpeg", "image/jpeg", true, false),
TAR_GZ("tar.gz", "application/gzip", true, false),
GZIP("gz", "application/gzip", true, false),
diff --git a/src/com/android/tradefed/targetprep/RebootTargetPreparer.java b/src/com/android/tradefed/targetprep/RebootTargetPreparer.java
index 5b9a87d..ff8b42b 100644
--- a/src/com/android/tradefed/targetprep/RebootTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/RebootTargetPreparer.java
@@ -13,20 +13,38 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.tradefed.targetprep;
import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
/** Target preparer that reboots the device. */
@OptionClass(alias = "reboot-preparer")
-public class RebootTargetPreparer extends BaseTargetPreparer {
+public class RebootTargetPreparer extends BaseTargetPreparer implements ITargetCleaner {
+
+ @Option(name = "pre-reboot", description = "Reboot the device during setUp.")
+ private boolean mPreReboot = true;
+
+ @Option(name = "post-reboot", description = "Reboot the device during tearDown.")
+ private boolean mPostReboot = false;
@Override
public void setUp(ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
- device.reboot();
+ if (mPreReboot) {
+ device.reboot();
+ }
+ }
+
+ @Override
+ public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
+ throws DeviceNotAvailableException {
+ if (mPostReboot) {
+ device.reboot();
+ }
}
}
diff --git a/src/com/android/tradefed/targetprep/TemperatureThrottlingWaiter.java b/src/com/android/tradefed/targetprep/TemperatureThrottlingWaiter.java
index f776296..598d78a 100644
--- a/src/com/android/tradefed/targetprep/TemperatureThrottlingWaiter.java
+++ b/src/com/android/tradefed/targetprep/TemperatureThrottlingWaiter.java
@@ -23,6 +23,9 @@
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.RunUtil;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
/** An {@link ITargetPreparer} that waits until device's temperature gets down to target */
@OptionClass(alias = "temperature-throttle-waiter")
public class TemperatureThrottlingWaiter extends BaseTargetPreparer {
@@ -46,11 +49,10 @@
public static final String DEVICE_TEMPERATURE_FILE_PATH_NAME = "device-temperature-file-path";
@Option(
- name = DEVICE_TEMPERATURE_FILE_PATH_NAME,
- description =
- "Name of file that contains device"
- + "temperature. Example: /sys/class/hwmon/hwmon1/device/msm_therm"
- )
+ 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 = "target-temperature", description = "Target Temperature that device should have;"
@@ -110,23 +112,30 @@
String result = device.executeShellCommand(
String.format("cat %s", fileName)).trim();
CLog.i(String.format("Temperature file output : %s", result));
- // example output : Result:30 Raw:7f6f
+
if (result == null || result.contains("No such file or directory")) {
throw new TargetSetupError(String.format("File %s doesn't exist", fileName),
device.getDeviceDescriptor());
- } else if (!result.toLowerCase().startsWith("result:")) {
- throw new TargetSetupError(
- String.format("file content is not as expected. Content : %s", result),
- device.getDeviceDescriptor());
}
- try {
- deviceTemp = Integer.parseInt(result.split(" ")[0].split(":")[1].trim());
- } catch (NumberFormatException numEx) {
- CLog.e(String.format("Temperature is not of right format %s", numEx.getMessage()));
- throw numEx;
+ // temperature raw format example output : Result:30 Raw:7f6f
+ final Pattern TEMPERATURE_RAW_FORMAT_REGEX = Pattern.compile("Result:(\\d+)\\sRaw:(\\w+)");
+ // temperature format example output : 30
+ final Pattern TEMPERATURE_FORMAT_REGEX = Pattern.compile("\\d+");
+ Matcher matcher = TEMPERATURE_RAW_FORMAT_REGEX.matcher(result);
+ if (matcher.find()) {
+ deviceTemp = Integer.parseInt(matcher.group(1));
+ } else {
+ matcher = TEMPERATURE_FORMAT_REGEX.matcher(result);
+ if (matcher.find()) {
+ deviceTemp = Integer.parseInt(matcher.group());
+ } else {
+ throw new TargetSetupError(
+ String.format("file content is not as expected. Content : %s", result),
+ device.getDeviceDescriptor());
+ }
}
return deviceTemp;
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/tradefed/testtype/GTest.java b/src/com/android/tradefed/testtype/GTest.java
index d81ec95..9da6620 100644
--- a/src/com/android/tradefed/testtype/GTest.java
+++ b/src/com/android/tradefed/testtype/GTest.java
@@ -16,6 +16,8 @@
package com.android.tradefed.testtype;
+import static com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain.GCOV;
+
import com.android.ddmlib.FileListingService;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.tradefed.config.Option;
@@ -25,7 +27,9 @@
import com.android.tradefed.device.ITestDevice;
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.FileUtil;
+import com.android.tradefed.util.NativeCodeCoverageFlusher;
import com.google.common.annotations.VisibleForTesting;
@@ -34,6 +38,7 @@
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -58,6 +63,30 @@
description = "Stops the Java application runtime before test execution.")
private boolean mStopRuntime = false;
+ /** @deprecated Use the --coverage-flush option in CoverageOptions instead. */
+ @Deprecated
+ @Option(
+ name = "coverage-flush",
+ description = "Forces coverage data to be flushed at the end of the test."
+ )
+ private boolean mCoverageFlush = false;
+
+ /** @deprecated Use the --coverage-processes option in CoverageOptions instead. */
+ @Deprecated
+ @Option(
+ name = "coverage-processes",
+ description = "Name of processes to collect coverage data from."
+ )
+ private List<String> mCoverageProcesses = new ArrayList<>();
+
+ /** @deprecated Merged into the --coverage-flush option in CoverageOptions instead. */
+ @Deprecated
+ @Option(
+ name = "coverage-clear-before-test",
+ description = "Clears all coverage counters before test execution."
+ )
+ private boolean mCoverageClearBeforeTest = true;
+
// Max characters allowed for executing GTest via command line
private static final int GTEST_CMD_CHAR_LIMIT = 1000;
/**
@@ -344,9 +373,20 @@
mDevice.executeShellCommand("stop");
}
// Insert the coverage listener if code coverage collection is enabled.
- listener = addNativeCoverageListenerIfEnabled(mDevice, listener);
+ listener = addNativeCoverageListenerIfEnabled(listener);
+ NativeCodeCoverageFlusher flusher =
+ new NativeCodeCoverageFlusher(mDevice, getCoverageOptions().getCoverageProcesses());
+
Throwable throwable = null;
try {
+ if (getCoverageOptions().isCoverageEnabled()) {
+ 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;
@@ -360,4 +400,21 @@
}
}
}
+
+ /**
+ * 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/src/com/android/tradefed/testtype/GTestBase.java b/src/com/android/tradefed/testtype/GTestBase.java
index a03e907..f003160 100644
--- a/src/com/android/tradefed/testtype/GTestBase.java
+++ b/src/com/android/tradefed/testtype/GTestBase.java
@@ -17,12 +17,14 @@
package com.android.tradefed.testtype;
import com.android.ddmlib.IShellOutputReceiver;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionCopier;
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
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.google.common.annotations.VisibleForTesting;
@@ -36,6 +38,7 @@
/** The base class of gTest */
public abstract class GTestBase
implements IRemoteTest,
+ IConfigurationReceiver,
ITestFilterReceiver,
IRuntimeHintProvider,
ITestCollector,
@@ -89,6 +92,8 @@
isTimeVal = true)
private long mMaxTestTimeMs = 1 * 60 * 1000L;
+ /** @deprecated use --coverage in CoverageOptions instead. */
+ @Deprecated
@Option(
name = "coverage",
description =
@@ -166,6 +171,14 @@
private int mShardIndex = 0;
private boolean mIsSharded = false;
+ private IConfiguration mConfiguration = null;
+
+ /** {@inheritDoc} */
+ @Override
+ public void setConfiguration(IConfiguration configuration) {
+ mConfiguration = configuration;
+ }
+
/**
* Set the Android native test module to run.
*
@@ -484,6 +497,10 @@
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));
@@ -517,21 +534,6 @@
}
/**
- * Adds a {@link NativeCodeCoverageListener} to the chain if code coverage is enabled.
- *
- * @param device the device to pull the coverage results from
- * @param listener the original listener
- * @return a chained listener if code coverage is enabled, otherwise the original listener
- */
- protected ITestInvocationListener addNativeCoverageListenerIfEnabled(
- ITestDevice device, ITestInvocationListener listener) {
- if (mCoverage) {
- return new NativeCodeCoverageListener(device, listener);
- }
- return listener;
- }
-
- /**
* Make a best effort attempt to retrieve a meaningful short descriptive message for given
* {@link Exception}
*
@@ -590,4 +592,15 @@
}
return shard;
}
+
+ /**
+ * 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();
+ }
}
diff --git a/src/com/android/tradefed/testtype/InstrumentationFileTest.java b/src/com/android/tradefed/testtype/InstrumentationFileTest.java
index 0361393..be791e8 100644
--- a/src/com/android/tradefed/testtype/InstrumentationFileTest.java
+++ b/src/com/android/tradefed/testtype/InstrumentationFileTest.java
@@ -81,6 +81,7 @@
mInstrumentationTest = createInstrumentationTest();
// copy all options from the original InstrumentationTest
OptionCopier.copyOptions(instrumentationTest, mInstrumentationTest);
+ mInstrumentationTest.setConfiguration(instrumentationTest.getConfiguration());
mInstrumentationTest.setDevice(instrumentationTest.getDevice());
mInstrumentationTest.setForceAbi(instrumentationTest.getForceAbi());
mInstrumentationTest.setReRunUsingTestFile(true);
diff --git a/src/com/android/tradefed/testtype/InstrumentationSerialTest.java b/src/com/android/tradefed/testtype/InstrumentationSerialTest.java
index ef2de3c..bf2eb98 100644
--- a/src/com/android/tradefed/testtype/InstrumentationSerialTest.java
+++ b/src/com/android/tradefed/testtype/InstrumentationSerialTest.java
@@ -71,6 +71,7 @@
throws ConfigurationException {
InstrumentationTest runner = new InstrumentationTest();
OptionCopier.copyOptions(instrumentationTest, runner);
+ runner.setConfiguration(instrumentationTest.getConfiguration());
runner.setDevice(instrumentationTest.getDevice());
runner.setForceAbi(instrumentationTest.getForceAbi());
// ensure testFile is not used.
diff --git a/src/com/android/tradefed/testtype/InstrumentationTest.java b/src/com/android/tradefed/testtype/InstrumentationTest.java
index 372bd5f..7c1c471 100644
--- a/src/com/android/tradefed/testtype/InstrumentationTest.java
+++ b/src/com/android/tradefed/testtype/InstrumentationTest.java
@@ -16,6 +16,9 @@
package com.android.tradefed.testtype;
+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;
@@ -26,6 +29,8 @@
import com.android.ddmlib.testrunner.InstrumentationResultParser;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.config.OptionClass;
@@ -43,10 +48,14 @@
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestRunResult;
import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner;
+import com.android.tradefed.testtype.coverage.CoverageOptions;
+import com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain;
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;
@@ -70,6 +79,7 @@
IResumableTest,
ITestCollector,
IAbiReceiver,
+ IConfigurationReceiver,
IInvocationContextReceiver,
IMetricCollectorReceiver {
@@ -244,6 +254,8 @@
)
protected boolean mDebug = false;
+ /** @deprecated Use the --coverage option in CoverageOptions instead. */
+ @Deprecated
@Option(
name = "coverage",
description =
@@ -297,14 +309,27 @@
private String mTestFilePathOnDevice = null;
private ListInstrumentationParser mListInstrumentationParser = null;
+ private NativeCodeCoverageListener mNativeCoverageListener = null;
private Set<String> mExtraDeviceListener = new HashSet<>();
private boolean mIsRerun = false;
+ private IConfiguration mConfiguration = null;
private IInvocationContext mContext;
private List<IMetricCollector> mCollectors = new ArrayList<>();
+ /** {@inheritDoc} */
+ @Override
+ public void setConfiguration(IConfiguration config) {
+ mConfiguration = config;
+ }
+
+ /** Gets the {@link IConfiguration} for this test. */
+ public IConfiguration getConfiguration() {
+ return mConfiguration;
+ }
+
/**
* {@inheritDoc}
*/
@@ -607,12 +632,6 @@
return mForceAbi;
}
- /** Sets the --coverage option for testing. */
- @VisibleForTesting
- void setCoverage(boolean coverageEnabled) {
- mCoverage = coverageEnabled;
- }
-
/** Sets the --merge-coverage-measurements option for testing. */
@VisibleForTesting
void setMergeCoverageMeasurements(boolean merge) {
@@ -854,16 +873,34 @@
if (mDebug) {
mRunner.setDebug(true);
}
- if (mCoverage) {
+ if (mConfiguration != null && mConfiguration.getCoverageOptions().isCoverageEnabled()) {
mRunner.addInstrumentationArg("coverage", "true");
}
- // Reruns do not create new listeners.
+ // Reruns do not create new listeners or clear coverage measurements.
if (!mIsRerun) {
listener = addBugreportListenerIfEnabled(listener);
listener = addJavaCoverageListenerIfEnabled(listener);
listener = addNativeCoverageListenerIfEnabled(listener);
+ // Clear coverage measurements on the device before running.
+ if (mConfiguration != null
+ && mConfiguration.getCoverageOptions().isCoverageFlushEnabled()) {
+ CoverageOptions options = mConfiguration.getCoverageOptions();
+
+ if (options.getCoverageToolchains().contains(Toolchain.GCOV)) {
+ NativeCodeCoverageFlusher flusher =
+ new NativeCodeCoverageFlusher(mDevice, options.getCoverageProcesses());
+ flusher.resetCoverage();
+ }
+
+ if (options.getCoverageToolchains().contains(Toolchain.JACOCO)) {
+ JavaCodeCoverageFlusher flusher =
+ new JavaCodeCoverageFlusher(mDevice, options.getCoverageProcesses());
+ flusher.resetCoverage();
+ }
+ }
+
// TODO: Convert to device-side collectors when possible.
for (IMetricCollector collector : mCollectors) {
if (collector.isDisabled()) {
@@ -922,8 +959,16 @@
* if this feature is disabled.
*/
ITestInvocationListener addJavaCoverageListenerIfEnabled(ITestInvocationListener listener) {
- if (mCoverage) {
- return new JavaCodeCoverageListener(getDevice(), mMergeCoverageMeasurements, 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;
}
@@ -933,8 +978,15 @@
* listener} if this feature is disabled.
*/
ITestInvocationListener addNativeCoverageListenerIfEnabled(ITestInvocationListener listener) {
- if (mCoverage) {
- return new NativeCodeCoverageListener(getDevice(), 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;
}
@@ -981,7 +1033,8 @@
}
}
// Don't re-run any completed tests, unless this is a coverage run.
- if (!mCoverage) {
+ if (mConfiguration != null
+ && !mConfiguration.getCoverageOptions().isCoverageEnabled()) {
expectedTests.removeAll(testTracker.getCurrentRunResults().getCompletedTests());
}
rerunTests(expectedTests, listener);
@@ -1013,7 +1066,16 @@
return;
}
+ if (mNativeCoverageListener != null) {
+ mNativeCoverageListener.setCollectOnTestEnd(false);
+ }
+
testReRunner.run(listener);
+
+ if (mNativeCoverageListener != null) {
+ mNativeCoverageListener.setCollectOnTestEnd(true);
+ mNativeCoverageListener.logCoverageMeasurements("rerun_merged");
+ }
}
@VisibleForTesting
diff --git a/src/com/android/tradefed/testtype/JavaCodeCoverageListener.java b/src/com/android/tradefed/testtype/JavaCodeCoverageListener.java
index c58e3c6..bc9929d 100644
--- a/src/com/android/tradefed/testtype/JavaCodeCoverageListener.java
+++ b/src/com/android/tradefed/testtype/JavaCodeCoverageListener.java
@@ -17,6 +17,7 @@
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;
@@ -25,7 +26,12 @@
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.collect.ImmutableList;
import org.jacoco.core.tools.ExecFileLoader;
@@ -43,18 +49,31 @@
public static final String COVERAGE_MEASUREMENT_KEY = "coverageFilePath";
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, boolean mergeMeasurements, ITestInvocationListener... listeners) {
+ 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
@@ -76,10 +95,7 @@
mExecFileLoader.save(mergedMeasurements, false);
// Save the merged measurement as a test log.
- try (FileInputStreamSource source =
- new FileInputStreamSource(mergedMeasurements, true)) {
- testLog("merged_runtime_coverage", LogDataType.COVERAGE, source);
- }
+ logCoverageMeasurement("merged_runtime_coverage", mergedMeasurements);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
@@ -93,37 +109,55 @@
super.testRunEnded(elapsedTime, runMetrics);
return;
}
- String devicePath = devicePathMetric.getMeasurements().getSingleString();
- if (devicePath == null) {
+ String testCoveragePath = devicePathMetric.getMeasurements().getSingleString();
+ if (testCoveragePath == null) {
super.testRunFailed("No coverage measurement.");
super.testRunEnded(elapsedTime, runMetrics);
return;
}
+ ImmutableList.Builder<String> devicePaths = ImmutableList.builder();
+ devicePaths.add(testCoveragePath);
+
File coverageFile = null;
try {
- coverageFile = mDevice.pullFile(devicePath);
- verifyNotNull(coverageFile, "Failed to pull the coverage file from %s", devicePath);
+ if (mCoverageOptions.isCoverageFlushEnabled()) {
+ devicePaths.addAll(mFlusher.forceCoverageFlush());
+ }
- // When merging, load the measurement data. Otherwise log the measurement
- // immediately.
- if (mMergeCoverageMeasurements) {
- mExecFileLoader.load(coverageFile);
- } else {
- try (FileInputStreamSource source =
- new FileInputStreamSource(coverageFile, true)) {
- testLog(
- mCurrentRunName + "_runtime_coverage",
- LogDataType.COVERAGE,
- source);
+ for (String devicePath : devicePaths.build()) {
+ coverageFile = mDevice.pullFile(devicePath);
+ verifyNotNull(
+ coverageFile, "Failed to pull the 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);
}
}
} catch (DeviceNotAvailableException | IOException e) {
throw new RuntimeException(e);
} finally {
- FileUtil.deleteFile(coverageFile);
super.testRunEnded(elapsedTime, runMetrics);
}
}
}
+
+ private void logCoverageMeasurement(String name, File coverageFile) throws IOException {
+ try (FileInputStreamSource source = new FileInputStreamSource(coverageFile, true)) {
+ testLog(name, LogDataType.COVERAGE, source);
+ }
+ }
}
diff --git a/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java b/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java
index dca83db..2349c34 100644
--- a/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java
+++ b/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java
@@ -17,6 +17,7 @@
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;
@@ -25,16 +26,16 @@
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.base.Splitter;
+import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
@@ -44,17 +45,48 @@
*/
public final class NativeCodeCoverageListener extends ResultForwarder {
- private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace/proc/self/cwd/out";
- private static final String COVERAGE_FILE_LIST_COMMAND =
- String.format("find %s -name '*.gcda'", NATIVE_COVERAGE_DEVICE_PATH);
+ private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace";
+ private static final String COVERAGE_TAR_PATH =
+ String.format("%s/coverage.tar.gz", 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 -cvzf %s -T -",
+ NATIVE_COVERAGE_DEVICE_PATH, COVERAGE_TAR_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
@@ -65,49 +97,61 @@
@Override
public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
- // Retrieve the list of .gcda files from the device. Don't use pullDir since it will
- // not pull from hidden directories. Keep the path of the files on the device in the
- // local directory so that the .gcda files can be mapped to the correct .gcno file.
- File localDir = null;
try {
- localDir = FileUtil.createTempDir("native_coverage");
+ if (mCollectCoverageOnTestEnd) {
+ logCoverageMeasurements(mCurrentRunName);
+ }
+ } finally {
+ super.testRunEnded(elapsedTime, runMetrics);
+ }
+ }
- // Enable abd root on the device, otherwise the list command will fail.
+ /** Pulls native coverage measurements from the device and logs them. */
+ public void logCoverageMeasurements(String runName) {
+ File coverageTarGz = 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.");
- String findResult = mDevice.executeShellCommand(COVERAGE_FILE_LIST_COMMAND);
- Path devicePathRoot = Paths.get(NATIVE_COVERAGE_DEVICE_PATH);
- for (String deviceFile : Splitter.on("\n").omitEmptyStrings().split(findResult)) {
- // Compute the relative path for the device file.
- Path relativePath = devicePathRoot.relativize(Paths.get(deviceFile));
- Path localFullPath = localDir.toPath().resolve(relativePath);
-
- // Create parent directories and pull the file.
- Files.createDirectories(localFullPath.getParent());
- verify(
- mDevice.pullFile(deviceFile, localFullPath.toFile()),
- "Failed to pull the coverage file from %s",
- deviceFile);
+ // Flush cross-process coverage.
+ if (mFlushCoverage) {
+ mFlusher.forceCoverageFlush();
}
- // Zip the contents of the localDir (not including localDir in the path) and log
- // the resulting file.
- File coverageZip =
- ZipUtil.createZip(
- Arrays.asList(localDir.listFiles()),
- mCurrentRunName + "_native_runtime_coverage");
+ // Compress coverage measurements on the device before pulling.
+ mDevice.executeShellCommand(ZIP_COVERAGE_FILES_COMMAND);
+ coverageTarGz = mDevice.pullFile(COVERAGE_TAR_PATH);
+ verifyNotNull(coverageTarGz, "Failed to pull the coverage file %s", COVERAGE_TAR_PATH);
+ mDevice.deleteFile(COVERAGE_TAR_PATH);
+
+ coverageZip = convertTarGzToZip(coverageTarGz);
try (FileInputStreamSource source = new FileInputStreamSource(coverageZip, true)) {
- testLog(
- mCurrentRunName + "_native_runtime_coverage",
- LogDataType.NATIVE_COVERAGE,
- source);
+ testLog(runName + "_native_runtime_coverage", LogDataType.NATIVE_COVERAGE, source);
}
} catch (DeviceNotAvailableException | IOException e) {
throw new RuntimeException(e);
} finally {
- FileUtil.recursiveDelete(localDir);
- super.testRunEnded(elapsedTime, runMetrics);
+ FileUtil.deleteFile(coverageTarGz);
+ FileUtil.deleteFile(coverageZip);
+ }
+ }
+
+ /**
+ * Converts a .tar.gz file to a .zip file.
+ *
+ * @param tarGz the .tar.gz file to convert
+ * @return a .zip file with the same contents
+ * @throws IOException
+ */
+ private File convertTarGzToZip(File tarGz) throws IOException {
+ File untarDir = null;
+ try {
+ untarDir = TarUtil.extractTarGzipToTemp(tarGz, "native_coverage");
+ return ZipUtil.createZip(Arrays.asList(untarDir.listFiles()), "native_coverage");
+ } finally {
+ FileUtil.recursiveDelete(untarDir);
}
}
}
diff --git a/src/com/android/tradefed/testtype/coverage/CoverageOptions.java b/src/com/android/tradefed/testtype/coverage/CoverageOptions.java
new file mode 100644
index 0000000..f1c5527
--- /dev/null
+++ b/src/com/android/tradefed/testtype/coverage/CoverageOptions.java
@@ -0,0 +1,98 @@
+/*
+ * 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.coverage;
+
+import com.android.tradefed.config.Option;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tradefed object to hold coverage options. */
+public final class CoverageOptions {
+
+ @Option(
+ name = "coverage",
+ description =
+ "Collect code coverage for this test run. Note that the build under test must be a "
+ + "coverage build or else this will fail."
+ )
+ private boolean mCoverage = false;
+
+ @Option(
+ name = "coverage-toolchain",
+ description =
+ "The coverage toolchains that were used to compile the coverage build to "
+ + "collect coverage from."
+ )
+ private List<Toolchain> mToolchains = new ArrayList<>();
+
+ public enum Toolchain {
+ GCOV,
+ JACOCO;
+ }
+
+ @Option(
+ name = "coverage-flush",
+ description = "Forces coverage data to be flushed at the end of the test."
+ )
+ private boolean mCoverageFlush = false;
+
+ @Option(
+ name = "coverage-processes",
+ description = "Name of processes to collect coverage data from."
+ )
+ private List<String> mCoverageProcesses = new ArrayList<>();
+
+ /**
+ * Returns whether coverage measurements should be collected from this run.
+ *
+ * @return whether to collect coverage measurements
+ */
+ public boolean isCoverageEnabled() {
+ return mCoverage;
+ }
+
+ /**
+ * Returns the coverage toolchains to collect coverage from.
+ *
+ * @return the toolchains to collect coverage from
+ */
+ public List<Toolchain> getCoverageToolchains() {
+ return ImmutableList.copyOf(mToolchains);
+ }
+
+ /**
+ * Returns whether coverage measurements should be flushed from running processes after the test
+ * has completed.
+ *
+ * @return whether to flush processes for coverage measurements after the test
+ */
+ public boolean isCoverageFlushEnabled() {
+ return mCoverageFlush;
+ }
+
+ /**
+ * Returns the name of processes to flush coverage from after the test has completed.
+ *
+ * @return a {@link List} of process names to flush coverage from after the test
+ */
+ public List<String> getCoverageProcesses() {
+ return ImmutableList.copyOf(mCoverageProcesses);
+ }
+}
diff --git a/src/com/android/tradefed/testtype/suite/AtestRunner.java b/src/com/android/tradefed/testtype/suite/AtestRunner.java
index 860c498..4be6e2a 100644
--- a/src/com/android/tradefed/testtype/suite/AtestRunner.java
+++ b/src/com/android/tradefed/testtype/suite/AtestRunner.java
@@ -25,13 +25,16 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.SubprocessResultsReporter;
import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.InstrumentationTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.ITestFilterReceiver;
+import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -77,6 +80,36 @@
)
private List<String> mIncludeFilters = new ArrayList<>();
+ @Option(
+ name = "tf-config-path",
+ description =
+ "Allows to run a specific TF configuration path."
+ )
+ private List<String> mTfConfigPaths = new ArrayList<>();
+
+ @Option(
+ name = "module-config-path",
+ description =
+ "Allows to run a specific module configuration path."
+ )
+ private List<File> mModuleConfigPaths = new ArrayList<>();
+
+ @Override
+ public LinkedHashMap<String, IConfiguration> loadingStrategy(Set<IAbi> abis,
+ List<File> testsDirs,
+ String suitePrefix, String suiteTag) {
+ if (mTfConfigPaths.isEmpty() && mModuleConfigPaths.isEmpty()) {
+ return super.loadingStrategy(abis, testsDirs, suitePrefix, suiteTag);
+ }
+ LinkedHashMap<String, IConfiguration> loadedConfigs = new LinkedHashMap<>();
+ loadedConfigs.putAll(
+ getModuleLoader().loadTfConfigsFromSpecifiedPaths(mTfConfigPaths, abis, suiteTag));
+ loadedConfigs.putAll(
+ getModuleLoader().loadConfigsFromSpecifiedPaths(
+ mModuleConfigPaths, abis, suiteTag));
+ return loadedConfigs;
+ }
+
@Override
public LinkedHashMap<String, IConfiguration> loadTests() {
// atest only needs to run on primary or specified abi.
diff --git a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
index e66b850..2e92d70 100644
--- a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
@@ -26,7 +26,10 @@
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.suite.params.IModuleParameter;
import com.android.tradefed.testtype.suite.params.ModuleParameters;
+import com.android.tradefed.testtype.suite.params.ModuleParametersHelper;
+import com.android.tradefed.testtype.suite.params.NegativeHandler;
import com.android.tradefed.util.ArrayUtil;
import com.google.common.annotations.VisibleForTesting;
@@ -48,6 +51,7 @@
public static final String INCLUDE_FILTER_OPTION = "include-filter";
public static final String EXCLUDE_FILTER_OPTION = "exclude-filter";
public static final String MODULE_OPTION = "module";
+ public static final char MODULE_OPTION_SHORT_NAME = 'm';
public static final String TEST_ARG_OPTION = "test-arg";
public static final String TEST_OPTION = "test";
public static final char TEST_OPTION_SHORT_NAME = 't';
@@ -70,7 +74,7 @@
@Option(
name = MODULE_OPTION,
- shortName = 'm',
+ shortName = MODULE_OPTION_SHORT_NAME,
description = "the test module to run. Only works for configuration in the tests dir.",
importance = Importance.IF_UNSET
)
@@ -168,6 +172,7 @@
private SuiteModuleLoader mModuleRepo;
private Map<String, List<SuiteTestFilter>> mIncludeFiltersParsed = new HashMap<>();
private Map<String, List<SuiteTestFilter>> mExcludeFiltersParsed = new HashMap<>();
+ private List<File> mConfigPaths = new ArrayList<>();
/** {@inheritDoc} */
@Override
@@ -230,6 +235,12 @@
public LinkedHashMap<String, IConfiguration> loadingStrategy(
Set<IAbi> abis, List<File> testsDirs, String suitePrefix, String suiteTag) {
LinkedHashMap<String, IConfiguration> loadedConfigs = new LinkedHashMap<>();
+ // Load and return directly the specific config files.
+ if (!mConfigPaths.isEmpty()) {
+ CLog.d("Loading the specified configs and skip loading from the resources.");
+ return getModuleLoader().loadConfigsFromSpecifiedPaths(mConfigPaths, abis, suiteTag);
+ }
+
// Load configs that are part of the resources
if (!mSkipJarLoading) {
loadedConfigs.putAll(
@@ -295,6 +306,11 @@
mModuleArgs.addAll(moduleArgs);
}
+ /** Clear the stored module args out */
+ void clearModuleArgs() {
+ mModuleArgs.clear();
+ }
+
/** Add config patterns */
public void addConfigPatterns(List<String> patterns) {
mConfigPatterns.addAll(patterns);
@@ -325,43 +341,64 @@
* @throws FileNotFoundException if any file is not found.
*/
protected void setupFilters(File testsDir) throws FileNotFoundException {
- if (mModuleName != null) {
- // If this option (-m / --module) is set only the matching unique module should run.
- Set<File> modules =
- SuiteModuleLoader.getModuleNamesMatching(
- testsDir, mSuitePrefix, String.format(".*%s.*.config", mModuleName));
- // If multiple modules match, do exact match.
- if (modules.size() > 1) {
- Set<File> newModules = new HashSet<>();
- String exactModuleName = String.format("%s.config", mModuleName);
- for (File module : modules) {
- if (module.getName().equals(exactModuleName)) {
- newModules.add(module);
- modules = newModules;
- break;
- }
+ if (mModuleName == null) {
+ if (mTestName != null) {
+ throw new IllegalArgumentException(
+ "Test name given without module name. Add --module <module-name>");
+ }
+ return;
+ }
+ // If this option (-m / --module) is set only the matching unique module should run.
+ Set<File> modules =
+ SuiteModuleLoader.getModuleNamesMatching(
+ testsDir, mSuitePrefix, String.format(".*%s.*.config", mModuleName));
+ // If multiple modules match, do exact match.
+ if (modules.size() > 1) {
+ Set<File> newModules = new HashSet<>();
+ String exactModuleName = String.format("%s.config", mModuleName);
+ for (File module : modules) {
+ if (module.getName().equals(exactModuleName)) {
+ newModules.add(module);
+ modules = newModules;
+ break;
}
}
- if (modules.size() == 0) {
- throw new IllegalArgumentException(
- String.format("No modules found matching %s", mModuleName));
- } else if (modules.size() > 1) {
- throw new IllegalArgumentException(
- String.format(
- "Multiple modules found matching %s:\n%s\nWhich one did you "
- + "mean?\n",
- mModuleName, ArrayUtil.join("\n", modules)));
- } else {
- File mod = modules.iterator().next();
- String moduleName = mod.getName().replace(".config", "");
- checkFilters(mIncludeFilters, moduleName);
- checkFilters(mExcludeFilters, moduleName);
- mIncludeFilters.add(
- new SuiteTestFilter(getRequestedAbi(), moduleName, mTestName).toString());
- }
- } else if (mTestName != null) {
+ }
+ if (modules.size() == 0) {
throw new IllegalArgumentException(
- "Test name given without module name. Add --module <module-name>");
+ String.format("No modules found matching %s", mModuleName));
+ } else if (modules.size() > 1) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Multiple modules found matching %s:\n%s\nWhich one did you "
+ + "mean?\n",
+ mModuleName, ArrayUtil.join("\n", modules)));
+ } else {
+ File mod = modules.iterator().next();
+ String moduleName = mod.getName().replace(".config", "");
+ checkFilters(mIncludeFilters, moduleName);
+ checkFilters(mExcludeFilters, moduleName);
+ mIncludeFilters.add(
+ new SuiteTestFilter(getRequestedAbi(), moduleName, mTestName).toString());
+ // Create the matching filters for the parameterized version of it if needed.
+ if (mEnableParameter) {
+ for (ModuleParameters param : ModuleParameters.values()) {
+ IModuleParameter moduleParam =
+ ModuleParametersHelper.getParameterHandler(param, false);
+ if (moduleParam == null) {
+ continue;
+ }
+ if (moduleParam instanceof NegativeHandler) {
+ continue;
+ }
+ String paramModuleName =
+ String.format(
+ "%s[%s]", moduleName, moduleParam.getParameterIdentifier());
+ mIncludeFilters.add(
+ new SuiteTestFilter(getRequestedAbi(), paramModuleName, mTestName)
+ .toString());
+ }
+ }
}
}
@@ -376,6 +413,21 @@
mExcludeFiltersParsed.clear();
}
+ /**
+ * Add the config path for {@link SuiteModuleLoader} to limit the search loading
+ * configurations.
+ *
+ * @param configPath A {@code File} with the absolute path of the configuration.
+ */
+ void addConfigPaths(File configPath) {
+ mConfigPaths.add(configPath);
+ }
+
+ /** Clear the stored config paths out. */
+ void clearConfigPaths() {
+ mConfigPaths.clear();
+ }
+
/* Helper method designed to remove filters in a list not applicable to the given module */
private static void checkFilters(Set<String> filters, String moduleName) {
Set<String> cleanedFilters = new HashSet<String>();
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index a65b8fc..e979820 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -411,6 +411,12 @@
continue;
}
filterPreparers(config.getValue(), mAllowedPreparers);
+
+ // Copy the CoverageOptions from the main configuration to the module configuration.
+ if (mMainConfiguration != null) {
+ config.getValue().setCoverageOptions(mMainConfiguration.getCoverageOptions());
+ }
+
filteredConfig.put(config.getKey(), config.getValue());
}
runConfig.clear();
diff --git a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
index 78d7b2f..ff1e948 100644
--- a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
+++ b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
@@ -110,6 +110,20 @@
mExcludedModuleParameters = excludedParams;
}
+ /** Main loading of configurations, looking into the specified files */
+ public LinkedHashMap<String, IConfiguration> loadConfigsFromSpecifiedPaths(
+ List<File> listConfigFiles,
+ Set<IAbi> abis,
+ String suiteTag) {
+ LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>();
+ for (File configFile : listConfigFiles) {
+ toRun.putAll(
+ loadOneConfig(
+ configFile.getName(), configFile.getAbsolutePath(), abis, suiteTag));
+ }
+ return toRun;
+ }
+
/** Main loading of configurations, looking into a folder */
public LinkedHashMap<String, IConfiguration> loadConfigsFromDirectory(
List<File> testsDirs,
@@ -123,11 +137,7 @@
ConfigurationUtil.getConfigNamesFileFromDirs(suitePrefix, testsDirs, patterns));
// Ensure stable initial order of configurations.
Collections.sort(listConfigFiles);
- for (File configFile : listConfigFiles) {
- toRun.putAll(
- loadOneConfig(
- configFile.getName(), configFile.getAbsolutePath(), abis, suiteTag));
- }
+ toRun.putAll(loadConfigsFromSpecifiedPaths(listConfigFiles, abis, suiteTag));
return toRun;
}
@@ -143,6 +153,16 @@
List<String> configs = configFactory.getConfigList(suitePrefix, false);
// Sort configs to ensure they are always evaluated and added in the same order.
Collections.sort(configs);
+ toRun.putAll(loadTfConfigsFromSpecifiedPaths(configs, abis, suiteTag));
+ return toRun;
+ }
+
+ /** Main loading of configurations, looking into the specified resources on the classpath. */
+ public LinkedHashMap<String, IConfiguration> loadTfConfigsFromSpecifiedPaths(
+ List<String> configs,
+ Set<IAbi> abis,
+ String suiteTag) {
+ LinkedHashMap<String, IConfiguration> toRun = new LinkedHashMap<>();
for (String configName : configs) {
toRun.putAll(loadOneConfig(configName, configName, abis, suiteTag));
}
@@ -156,17 +176,16 @@
*
* @param test The {@link IRemoteTest} that is being considered.
* @param abi The Abi we are currently working on.
- * @param name The name of the module.
+ * @param moduleId The id of the module (usually abi + module name).
* @param includeFilters The formatted and parsed include filters.
* @param excludeFilters The formatted and parsed exclude filters.
*/
public void addFiltersToTest(
IRemoteTest test,
IAbi abi,
- String name,
+ String moduleId,
Map<String, List<SuiteTestFilter>> includeFilters,
Map<String, List<SuiteTestFilter>> excludeFilters) {
- String moduleId = AbiUtils.createId(abi.getName(), name);
if (!(test instanceof ITestFilterReceiver)) {
CLog.e("Test in module %s does not implement ITestFilterReceiver.", moduleId);
return;
@@ -174,10 +193,10 @@
List<SuiteTestFilter> mdIncludes = getFilterList(includeFilters, moduleId);
List<SuiteTestFilter> mdExcludes = getFilterList(excludeFilters, moduleId);
if (!mdIncludes.isEmpty()) {
- addTestIncludes((ITestFilterReceiver) test, mdIncludes, name);
+ addTestIncludes((ITestFilterReceiver) test, mdIncludes, moduleId);
}
if (!mdExcludes.isEmpty()) {
- addTestExcludes((ITestFilterReceiver) test, mdExcludes, name);
+ addTestExcludes((ITestFilterReceiver) test, mdExcludes, moduleId);
}
}
@@ -402,9 +421,11 @@
}
private void addTestIncludes(
- ITestFilterReceiver test, List<SuiteTestFilter> includes, String name) {
+ ITestFilterReceiver test, List<SuiteTestFilter> includes, String moduleId) {
if (test instanceof ITestFileFilterReceiver) {
- File includeFile = createFilterFile(name, ".include", includes);
+ // module id can contain spaces, avoid them for file names.
+ String escapedFileName = moduleId.replaceAll(" ", "_");
+ File includeFile = createFilterFile(escapedFileName, ".include", includes);
((ITestFileFilterReceiver) test).setIncludeTestFile(includeFile);
} else {
// add test includes one at a time
@@ -561,7 +582,7 @@
*
* @param name The base name of the module
* @param id The base id name of the module.
- * @param fullId The full id of the module.
+ * @param fullId The full id of the module (usually abi + module name + parameters)
* @param config The module configuration.
* @param abi The abi of the module.
* @throws ConfigurationException
@@ -595,7 +616,7 @@
if (mTestOptions.containsKey(className)) {
config.injectOptionValues(mTestOptions.get(className));
}
- addFiltersToTest(test, abi, name, mIncludeFilters, mExcludeFilters);
+ addFiltersToTest(test, abi, fullId, mIncludeFilters, mExcludeFilters);
if (test instanceof IAbiReceiver) {
((IAbiReceiver) test).setAbi(abi);
}
diff --git a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
index c0dc16a..d32a1fc 100644
--- a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
+++ b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
@@ -18,17 +18,22 @@
import com.android.tradefed.config.ConfigurationDescriptor;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.Option;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.util.testmapping.TestInfo;
import com.android.tradefed.util.testmapping.TestMapping;
import com.android.tradefed.util.testmapping.TestOption;
+import com.google.common.annotations.VisibleForTesting;
+import java.io.File;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
+
/**
* Implementation of {@link BaseTestSuite} to run tests specified by option include-filter, or
* TEST_MAPPING files from build, as a suite.
@@ -56,6 +61,26 @@
)
private Set<String> mKeywords = new HashSet<>();
+ @Option(
+ name = "force-test-mapping-module",
+ description =
+ "Run the specified tests only. The tests loaded from all TEST_MAPPING files in "
+ + "the source code will be filtered again to force run the specified tests."
+ )
+ private Set<String> mTestModulesForced = new HashSet<>();
+
+ @Option(
+ name = "test-mapping-path",
+ description = "Run tests according to the test mapping path."
+ )
+ private List<String> mTestMappingPaths = new ArrayList<>();
+
+ @Option(
+ name = "use-test-mapping-path",
+ description = "Whether or not to run tests based on the given test mapping path."
+ )
+ private boolean mUseTestMappingPath = false;
+
/** Special definition in the test mapping structure. */
private static final String TEST_MAPPING_INCLUDE_FILTER = "include-filter";
@@ -78,10 +103,10 @@
*/
@Override
public LinkedHashMap<String, IConfiguration> loadTests() {
- // Map between test names and a list of test sources for each test.
- Map<String, List<String>> testsInTestMapping = new HashMap<>();
-
Set<String> includeFilter = getIncludeFilter();
+ // Name of the tests
+ Set<String> testNames = new HashSet<>();
+ Set<TestInfo> testInfosToRun = new HashSet<>();
if (mTestGroup == null && includeFilter.isEmpty()) {
throw new RuntimeException(
"At least one of the options, --test-mapping-test-group or --include-filter, "
@@ -91,95 +116,204 @@
throw new RuntimeException(
"Must specify --test-mapping-test-group when applying --test-mapping-keyword.");
}
+ if (mTestGroup == null && !mTestModulesForced.isEmpty()) {
+ throw new RuntimeException(
+ "Must specify --test-mapping-test-group when applying "
+ + "--force-test-mapping-module.");
+ }
if (mTestGroup != null && !includeFilter.isEmpty()) {
throw new RuntimeException(
"If options --test-mapping-test-group is set, option --include-filter should "
+ "not be set.");
}
+ if (!includeFilter.isEmpty() && !mTestMappingPaths.isEmpty()) {
+ throw new RuntimeException(
+ "If option --include-filter is set, option --test-mapping-path should "
+ + "not be set.");
+ }
if (mTestGroup != null) {
- Set<TestInfo> testsToRun =
+ if (!mTestMappingPaths.isEmpty()) {
+ TestMapping.setTestMappingPaths(mTestMappingPaths);
+ }
+ testInfosToRun =
TestMapping.getTests(
getBuildInfo(), mTestGroup, getPrioritizeHostConfig(), mKeywords);
- if (testsToRun.isEmpty()) {
+ if (!mTestModulesForced.isEmpty()) {
+ CLog.i("Filtering tests for the given names: %s", mTestModulesForced);
+ testInfosToRun =
+ testInfosToRun
+ .stream()
+ .filter(testInfo -> mTestModulesForced.contains(testInfo.getName()))
+ .collect(Collectors.toSet());
+ }
+ if (testInfosToRun.isEmpty()) {
throw new RuntimeException(
String.format("No test found for the given group: %s.", mTestGroup));
}
-
- // Name of the tests
- Set<String> testNames = new HashSet<>();
-
- Set<String> mappingIncludeFilters = new HashSet<>();
- Set<String> mappingExcludeFilters = new HashSet<>();
-
- // module-arg options compiled from test options for each test.
- Set<String> moduleArgs = new HashSet<>();
- for (TestInfo test : testsToRun) {
- boolean hasIncludeFilters = false;
- for (TestOption option : test.getOptions()) {
- switch (option.getName()) {
- // Handle include and exclude filter at the suite level to hide each
- // test runner specific implementation and option names related to filtering
- case TEST_MAPPING_INCLUDE_FILTER:
- hasIncludeFilters = true;
- mappingIncludeFilters.add(
- String.format("%s %s", test.getName(), option.getValue()));
- break;
- case TEST_MAPPING_EXCLUDE_FILTER:
- mappingExcludeFilters.add(
- String.format("%s %s", test.getName(), option.getValue()));
- break;
- default:
- String moduleArg =
- String.format("%s:%s", test.getName(), option.getName());
- if (option.getValue() != null && !option.getValue().isEmpty()) {
- moduleArg = String.format("%s:%s", moduleArg, option.getValue());
- }
- moduleArgs.add(moduleArg);
- break;
- }
- }
- if (!hasIncludeFilters) {
- testNames.add(test.getName());
- }
+ for (TestInfo testInfo : testInfosToRun) {
+ testNames.add(testInfo.getName());
}
-
- if (mappingIncludeFilters.isEmpty()) {
- setIncludeFilter(testNames);
- } else {
- mappingIncludeFilters.addAll(testNames);
- setIncludeFilter(mappingIncludeFilters);
- }
- if (!mappingExcludeFilters.isEmpty()) {
- setExcludeFilter(mappingExcludeFilters);
- }
- addModuleArgs(moduleArgs);
-
- for (TestInfo test : testsToRun) {
- List<String> testSources = null;
- // TODO(b/117880789): tests may not be grouped by name once that bug is fixed.
- // Update the dictionary with better keys.
- if (testsInTestMapping.containsKey(test.getName())) {
- testSources = testsInTestMapping.get(test.toString());
- } else {
- testSources = new ArrayList<String>();
- testsInTestMapping.put(test.getName(), testSources);
- }
- testSources.addAll(test.getSources());
- }
+ setIncludeFilter(testNames);
}
+ // load all the configurations with include-filter injected.
LinkedHashMap<String, IConfiguration> testConfigs = super.loadTests();
+
+ // Create and inject individual tests by calling super.loadTests() with each test info.
for (Map.Entry<String, IConfiguration> entry : testConfigs.entrySet()) {
+ List<IRemoteTest> allTests = new ArrayList<>();
+ IConfiguration moduleConfig = entry.getValue();
ConfigurationDescriptor configDescriptor =
- entry.getValue().getConfigurationDescription();
- if (testsInTestMapping.containsKey(configDescriptor.getModuleName())) {
- configDescriptor.addMetaData(
- TestMapping.TEST_SOURCES,
- testsInTestMapping.get(configDescriptor.getModuleName()));
+ moduleConfig.getConfigurationDescription();
+ String moduleName = configDescriptor.getModuleName();
+ String configPath = moduleConfig.getName();
+ Set<TestInfo> testInfos = getTestInfos(testInfosToRun, moduleName);
+ allTests.addAll(
+ createIndividualTests(
+ testInfos, configPath));
+ if (!allTests.isEmpty()) {
+ // Set back to IConfiguration only if IRemoteTests are created.
+ moduleConfig.setTests(allTests);
+ // Set test sources to ConfigurationDescriptor.
+ List<String> testSources = getTestSources(testInfos);
+ configDescriptor.addMetaData(TestMapping.TEST_SOURCES, testSources);
+ }
+ }
+ return testConfigs;
+ }
+
+ /**
+ * Create individual tests with test infos for a module.
+ *
+ * @param testInfos A {@code Set<TestInfo>} containing multiple test options.
+ * @param configPath A {@code String} of configuration path.
+ * @return The {@link List<IRemoteTest>} that are injected with the test options.
+ */
+ @VisibleForTesting
+ List<IRemoteTest> createIndividualTests(Set<TestInfo> testInfos, String configPath) {
+ List<IRemoteTest> tests = new ArrayList<>();
+ if (configPath == null) {
+ throw new RuntimeException(String.format("Configuration path is null."));
+ }
+ File configFie = new File(configPath);
+ if (!configFie.exists()) {
+ throw new RuntimeException(
+ String.format("Configuration path: %s doesn't exits.", configPath));
+ }
+ // De-duplicate test infos so that there won't be duplicate test options.
+ testInfos = dedupTestInfos(testInfos);
+ for (TestInfo testInfo : testInfos) {
+ // Clean up all the test options injected in SuiteModuleLoader.
+ super.cleanUpSuiteSetup();
+ super.clearModuleArgs();
+ clearConfigPaths();
+ // Set config path to BaseTestSuite to limit the search.
+ addConfigPaths(configFie);
+ // Inject the test options from each test info to SuiteModuleLoader.
+ parseOptions(testInfo);
+ LinkedHashMap<String, IConfiguration> config = super.loadTests();
+ for (Map.Entry<String, IConfiguration> entry : config.entrySet()) {
+ tests.addAll(entry.getValue().getTests());
+ }
+ }
+ return tests;
+ }
+
+ /**
+ * Get a list of path of TEST_MAPPING for a module.
+ *
+ * @param testInfos A {@code Set<TestInfo>} containing multiple test options.
+ * @return A {@code List<String>} of TEST_MAPPING path.
+ */
+ @VisibleForTesting
+ List<String> getTestSources(Set<TestInfo> testInfos) {
+ List<String> testSources = new ArrayList<>();
+ for (TestInfo testInfo : testInfos) {
+ testSources.addAll(testInfo.getSources());
+ }
+ return testSources;
+ }
+
+ /**
+ * Parse the test options for the test info.
+ *
+ * @param testInfo A {@code Set<TestInfo>} containing multiple test options.
+ */
+ @VisibleForTesting
+ void parseOptions(TestInfo testInfo) {
+ Set<String> mappingIncludeFilters = new HashSet<>();
+ Set<String> mappingExcludeFilters = new HashSet<>();
+ // module-arg options compiled from test options for each test.
+ Set<String> moduleArgs = new HashSet<>();
+ Set<String> testNames = new HashSet<>();
+ for (TestOption option : testInfo.getOptions()) {
+ switch (option.getName()) {
+ // Handle include and exclude filter at the suite level to hide each
+ // test runner specific implementation and option names related to filtering
+ case TEST_MAPPING_INCLUDE_FILTER:
+ mappingIncludeFilters.add(
+ String.format("%s %s", testInfo.getName(), option.getValue()));
+ break;
+ case TEST_MAPPING_EXCLUDE_FILTER:
+ mappingExcludeFilters.add(
+ String.format("%s %s", testInfo.getName(), option.getValue()));
+ break;
+ default:
+ String moduleArg =
+ String.format("%s:%s", testInfo.getName(), option.getName());
+ if (option.getValue() != null && !option.getValue().isEmpty()) {
+ moduleArg = String.format("%s:%s", moduleArg, option.getValue());
+ }
+ moduleArgs.add(moduleArg);
+ break;
}
}
- return testConfigs;
+ if (mappingIncludeFilters.isEmpty()) {
+ testNames.add(testInfo.getName());
+ setIncludeFilter(testNames);
+ } else {
+ setIncludeFilter(mappingIncludeFilters);
+ }
+ if (!mappingExcludeFilters.isEmpty()) {
+ setExcludeFilter(mappingExcludeFilters);
+ }
+ addModuleArgs(moduleArgs);
+ }
+
+ /**
+ * De-duplicate test infos with the same test options.
+ *
+ * @param testInfos A {@code Set<TestInfo>} containing multiple test options.
+ * @return A {@code Set<TestInfo>} of tests without duplicated test options.
+ */
+ @VisibleForTesting
+ Set<TestInfo> dedupTestInfos(Set<TestInfo> testInfos) {
+ Set<String> nameOptions = new HashSet<>();
+ Set<TestInfo> dedupTestInfos = new HashSet<>();
+ for (TestInfo testInfo : testInfos) {
+ String nameOption = testInfo.getName() + testInfo.getOptions().toString();
+ if (!nameOptions.contains(nameOption)) {
+ dedupTestInfos.add(testInfo);
+ nameOptions.add(nameOption);
+ }
+ }
+ return dedupTestInfos;
+ }
+
+ /**
+ * Get the test infos for the given module name.
+ *
+ * @param testInfos A {@code Set<TestInfo>} containing multiple test options.
+ * @param moduleName A {@code String} name of a test module.
+ * @return A {@code Set<TestInfo>} of tests for a module.
+ */
+ @VisibleForTesting
+ Set<TestInfo> getTestInfos(Set<TestInfo> testInfos, String moduleName) {
+ return testInfos
+ .stream()
+ .filter(testInfo -> moduleName.equals(testInfo.getName()))
+ .collect(Collectors.toSet());
}
}
diff --git a/src/com/android/tradefed/testtype/suite/retry/RetryRescheduler.java b/src/com/android/tradefed/testtype/suite/retry/RetryRescheduler.java
index 3206494..8dfbccb 100644
--- a/src/com/android/tradefed/testtype/suite/retry/RetryRescheduler.java
+++ b/src/com/android/tradefed/testtype/suite/retry/RetryRescheduler.java
@@ -75,6 +75,13 @@
+ "and \"not_executed\".")
private RetryType mRetryType = null;
+ @Option(
+ name = BaseTestSuite.MODULE_OPTION,
+ shortName = BaseTestSuite.MODULE_OPTION_SHORT_NAME,
+ description = "the test module to run. Only works for configuration in the tests dir."
+ )
+ private String mModuleName = null;
+
/**
* It's possible to add extra exclusion from the rerun. But these tests will not change their
* state.
@@ -224,6 +231,22 @@
types.add(mRetryType);
}
+ // Expand the --module option in case no abi is specified.
+ Set<String> expandedModuleOption = new HashSet<>();
+ if (mModuleName != null) {
+ SuiteTestFilter moduleFilter = SuiteTestFilter.createFrom(mModuleName);
+ expandedModuleOption.add(mModuleName);
+ if (moduleFilter.getAbi() == null) {
+ Set<String> abis = AbiUtils.getAbisSupportedByCompatibility();
+ for (String abi : abis) {
+ SuiteTestFilter namingFilter =
+ new SuiteTestFilter(
+ abi, moduleFilter.getName(), moduleFilter.getTest());
+ expandedModuleOption.add(namingFilter.toString());
+ }
+ }
+ }
+
// Expand the exclude-filter in case no abi is specified.
Set<String> extendedExcludeRetryFilters = new HashSet<>();
for (String excludeFilter : mExcludeFilters) {
@@ -245,6 +268,8 @@
for (TestRunResult moduleResult : results.getMergedTestRunResults()) {
// If the module is explicitly excluded from retries, preserve the original results.
if (!extendedExcludeRetryFilters.contains(moduleResult.getName())
+ && (expandedModuleOption.isEmpty()
+ || expandedModuleOption.contains(moduleResult.getName()))
&& RetryResultHelper.shouldRunModule(moduleResult, types)) {
if (types.contains(RetryType.NOT_EXECUTED)) {
// Clear the run failure since we are attempting to rerun all non-executed
diff --git a/src/com/android/tradefed/util/JavaCodeCoverageFlusher.java b/src/com/android/tradefed/util/JavaCodeCoverageFlusher.java
new file mode 100644
index 0000000..88a562a
--- /dev/null
+++ b/src/com/android/tradefed/util/JavaCodeCoverageFlusher.java
@@ -0,0 +1,146 @@
+/*
+ * 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.util;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A utility class that resets and forces a flush of Java code coverage measurements from processes
+ * running on the device.
+ */
+public class JavaCodeCoverageFlusher {
+
+ private static final String COVERAGE_RESET_FORMAT =
+ "am attach-agent %s /system/lib/libdumpcoverage.so=reset";
+ private static final String COVERAGE_FLUSH_FORMAT =
+ "am attach-agent %s /system/lib/libdumpcoverage.so=dump:%s";
+ private static final String PACKAGE_PREFIX = "package:";
+
+ private final ITestDevice mDevice;
+ private final List<String> mProcessNames;
+
+ public JavaCodeCoverageFlusher(ITestDevice device, List<String> processNames) {
+ mDevice = device;
+ mProcessNames = ImmutableList.copyOf(processNames);
+ }
+
+ /** Retrieves the name of all running processes on the device. */
+ private List<String> getAllProcessNames() throws DeviceNotAvailableException {
+ List<ProcessInfo> processes = PsParser.getProcesses(mDevice.executeShellCommand("ps -e"));
+ List<String> names = new ArrayList<>();
+
+ for (ProcessInfo pinfo : processes) {
+ names.add(pinfo.getName());
+ }
+
+ return names;
+ }
+
+ /** Retrieves the set of all installed packages on the device. */
+ private Set<String> getAllPackages() throws DeviceNotAvailableException {
+ Splitter splitter = Splitter.on('\n').trimResults().omitEmptyStrings();
+ String pmListPackages = mDevice.executeShellCommand("pm list packages -a");
+
+ List<String> lines = splitter.splitToList(pmListPackages);
+ ImmutableSet.Builder<String> packages = ImmutableSet.builder();
+
+ // Strip "package:" prefix from the output.
+ for (String line : lines) {
+ if (line.startsWith(PACKAGE_PREFIX)) {
+ line = line.substring(PACKAGE_PREFIX.length());
+ }
+
+ packages.add(line);
+ }
+
+ return packages.build();
+ }
+
+ /**
+ * Resets the Java code coverage counters for the given processes.
+ *
+ * @throws DeviceNotAvailableException
+ */
+ public void resetCoverage() throws DeviceNotAvailableException {
+ List<String> processNames = mProcessNames;
+
+ if (processNames.isEmpty()) {
+ processNames = getAllProcessNames();
+ }
+
+ Set<String> javaPackages = getAllPackages();
+
+ // Attempt to reset the coverage measurements for the given processes.
+ for (String processName :
+ Sets.intersection(javaPackages, ImmutableSet.copyOf(processNames))) {
+ String pid = mDevice.getProcessPid(processName);
+
+ if (pid != null) {
+ mDevice.executeShellCommand(String.format(COVERAGE_RESET_FORMAT, processName));
+ }
+ }
+
+ // Reset coverage for system_server.
+ mDevice.executeShellCommand("cmd coverage reset");
+ }
+
+ /**
+ * Forces a flush of Java coverage data from processes running on the device.
+ *
+ * @return a list of the coverage files generated on the device.
+ * @throws DeviceNotAvailableException
+ */
+ public List<String> forceCoverageFlush() throws DeviceNotAvailableException {
+ List<String> processNames = mProcessNames;
+ if (processNames.isEmpty()) {
+ processNames = getAllProcessNames();
+ }
+
+ List<String> coverageFiles = new ArrayList<>();
+
+ Set<String> javaPackages = getAllPackages();
+
+ // Flush coverage for the given processes, if the process exists and is a Java process.
+ for (String processName :
+ Sets.intersection(javaPackages, ImmutableSet.copyOf(processNames))) {
+ String pid = mDevice.getProcessPid(processName);
+
+ if (pid != null) {
+ String outputPath = String.format("/data/misc/trace/%s-%s.ec", processName, pid);
+ mDevice.executeShellCommand(
+ String.format(COVERAGE_FLUSH_FORMAT, processName, outputPath));
+ coverageFiles.add(outputPath);
+ }
+ }
+
+ // Flush coverage for system_server.
+ mDevice.executeShellCommand("cmd coverage dump /data/misc/trace/system_server.ec");
+ coverageFiles.add("/data/misc/trace/system_server.ec");
+
+ return coverageFiles;
+ }
+}
diff --git a/src/com/android/tradefed/util/NativeCodeCoverageFlusher.java b/src/com/android/tradefed/util/NativeCodeCoverageFlusher.java
index b53542a..7c0ae3b 100644
--- a/src/com/android/tradefed/util/NativeCodeCoverageFlusher.java
+++ b/src/com/android/tradefed/util/NativeCodeCoverageFlusher.java
@@ -35,18 +35,21 @@
private static final String CLEAR_NATIVE_COVERAGE_FILES = "rm -rf /data/misc/trace/*";
private final ITestDevice mDevice;
+ private final List<String> mProcessNames;
- public NativeCodeCoverageFlusher(ITestDevice device) {
+ public NativeCodeCoverageFlusher(ITestDevice device, List<String> processNames) {
mDevice = device;
+ mProcessNames = processNames;
}
/**
- * Clears coverage measurements from disk on the device. Device must be in adb root.
+ * Resets native coverage counters for processes running on the device and clears any existing
+ * coverage measurements from disk. Device must be in adb root.
*
* @throws DeviceNotAvailableException
*/
- public void clearCoverageMeasurements() throws DeviceNotAvailableException {
- checkState(mDevice.isAdbRoot(), "adb root is required to clear coverage files.");
+ public void resetCoverage() throws DeviceNotAvailableException {
+ forceCoverageFlush();
mDevice.executeShellCommand(CLEAR_NATIVE_COVERAGE_FILES);
}
@@ -54,20 +57,18 @@
* Forces a flush of native coverage data from processes running on the device. Device must be
* in adb root.
*
- * @param processNames the name of processes to target for flushing; if empty, flushes from all
- * running native processes on the device.
* @throws DeviceNotAvailableException
*/
- public void forceCoverageFlush(List<String> processNames) throws DeviceNotAvailableException {
+ public void forceCoverageFlush() throws DeviceNotAvailableException {
checkState(mDevice.isAdbRoot(), "adb root is required to flush native coverage data.");
- if ((processNames == null) || processNames.isEmpty()) {
+ if ((mProcessNames == null) || mProcessNames.isEmpty()) {
// Use the special pid -1 to trigger a coverage flush of all running processes.
mDevice.executeShellCommand(String.format(COVERAGE_FLUSH_COMMAND_FORMAT, "-1"));
} else {
// Look up the pid of the processes to send them the coverage flush signal.
StringJoiner pidString = new StringJoiner(" ");
- for (String processName : processNames) {
+ for (String processName : mProcessNames) {
String pid = mDevice.getProcessPid(processName);
if (pid == null) {
CLog.w("Did not find pid for process \"%s\".", processName);
diff --git a/src/com/android/tradefed/util/TarUtil.java b/src/com/android/tradefed/util/TarUtil.java
index d9dfb2a..672696f 100644
--- a/src/com/android/tradefed/util/TarUtil.java
+++ b/src/com/android/tradefed/util/TarUtil.java
@@ -81,6 +81,15 @@
}
} else {
CLog.i(String.format("Creating output file %s.", outputFile.getAbsolutePath()));
+ final File parent = outputFile.getParentFile();
+ if (parent != null && !parent.exists()) {
+ if (!parent.mkdirs()) {
+ throw new IOException(
+ String.format(
+ "Couldn't create directory %s.",
+ parent.getAbsolutePath()));
+ }
+ }
final OutputStream outputFileStream = new FileOutputStream(outputFile);
IOUtils.copy(debInputStream, outputFileStream);
StreamUtil.close(outputFileStream);
@@ -153,6 +162,33 @@
}
/**
+ * Untar and ungzip a tar.gz file to a temp directory.
+ *
+ * @param targzFile the tar.gz file to extract.
+ * @param nameHint the prefix for the temp directory.
+ * @return the temp directory.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public static File extractTarGzipToTemp(File targzFile, String nameHint)
+ throws FileNotFoundException, IOException {
+ File unGzipDir = null;
+ File unTarDir = null;
+ try {
+ unGzipDir = FileUtil.createTempDir("extractTarGzip");
+ File tarFile = TarUtil.unGzip(targzFile, unGzipDir);
+ unTarDir = FileUtil.createTempDir(nameHint);
+ TarUtil.unTar(tarFile, unTarDir);
+ return unTarDir;
+ } catch (IOException e) {
+ FileUtil.recursiveDelete(unTarDir);
+ throw e;
+ } finally {
+ FileUtil.recursiveDelete(unGzipDir);
+ }
+ }
+
+ /**
* Helper to extract and log to the reporters a tar gz file and its content
*
* @param listener the {@link ITestLogger} where to log the files.
diff --git a/src/com/android/tradefed/util/testmapping/TestMapping.java b/src/com/android/tradefed/util/testmapping/TestMapping.java
index 79da7c2..93419c0 100644
--- a/src/com/android/tradefed/util/testmapping/TestMapping.java
+++ b/src/com/android/tradefed/util/testmapping/TestMapping.java
@@ -34,6 +34,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -42,6 +43,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -63,6 +66,24 @@
private static final String DISABLED_PRESUBMIT_TESTS_FILE = "disabled-presubmit-tests";
private Map<String, Set<TestInfo>> mTestCollection = null;
+ // Pattern used to identify comments start with "//" or "#" in TEST_MAPPING.
+ private static final Pattern COMMENTS_REGEX = Pattern.compile(
+ "(?m)[\\s\\t]*(//|#).*|(\".*?\")");
+ private static final Set<String> COMMENTS = new HashSet<>(Arrays.asList("#", "//"));
+
+ private static List<String> mTestMappingRelativePaths = new ArrayList<>();
+
+ /**
+ * Set the TEST_MAPPING paths inside of TEST_MAPPINGS_ZIP to limit loading the TEST_MAPPING.
+ *
+ * @param relativePaths A {@code List<String>} of TEST_MAPPING paths relative to
+ * TEST_MAPPINGS_ZIP.
+ */
+ public static void setTestMappingPaths(List<String> relativePaths) {
+ mTestMappingRelativePaths.clear();
+ mTestMappingRelativePaths.addAll(relativePaths);
+ }
+
/**
* Constructor to create a {@link TestMapping} object from a path to TEST_MAPPING file.
@@ -75,7 +96,8 @@
String relativePath = testMappingsDir.relativize(path.getParent()).toString();
String errorMessage = null;
try {
- String content = String.join("", Files.readAllLines(path, StandardCharsets.UTF_8));
+ String content = removeComments(
+ String.join("\n", Files.readAllLines(path, StandardCharsets.UTF_8)));
if (content != null) {
JSONTokener tokener = new JSONTokener(content);
JSONObject root = new JSONObject(tokener);
@@ -139,6 +161,26 @@
}
/**
+ * Helper to remove comments in a TEST_MAPPING file to valid format. Only "//" and "#" are
+ * regarded as comments.
+ *
+ * @param jsonContent A {@link String} of json which content is from a TEST_MAPPING file.
+ * @return A {@link String} of valid json without comments.
+ */
+ @VisibleForTesting
+ static String removeComments(String jsonContent) {
+ StringBuffer out = new StringBuffer();
+ Matcher matcher = COMMENTS_REGEX.matcher(jsonContent);
+ while (matcher.find()) {
+ if (COMMENTS.contains(matcher.group(1))) {
+ matcher.appendReplacement(out, "");
+ }
+ }
+ matcher.appendTail(out);
+ return out.toString();
+ }
+
+ /**
* Helper to get all tests set in a TEST_MAPPING file for a given group.
*
* @param testGroup A {@link String} of the test group.
@@ -219,6 +261,39 @@
}
/**
+ * Helper to get all TEST_MAPPING paths relative to TEST_MAPPINGS_ZIP.
+ *
+ * @param testMappingsRootPath The {@link Path} to a test mappings zip path.
+ * @return A {@code Set<Path>} of all the TEST_MAPPING paths relative to TEST_MAPPINGS_ZIP.
+ */
+ @VisibleForTesting
+ static Set<Path> getAllTestMappingPaths(Path testMappingsRootPath) {
+ Set<Path> allTestMappingPaths = new HashSet<>();
+ for (String path : mTestMappingRelativePaths) {
+ boolean hasAdded = false;
+ Path testMappingPath = testMappingsRootPath.resolve(path);
+ // Recursively find the TEST_MAPPING file until reaching to testMappingsRootPath.
+ while (!testMappingPath.equals(testMappingsRootPath)) {
+ if (testMappingPath.resolve(TEST_MAPPING).toFile().exists()) {
+ hasAdded = true;
+ CLog.d("Adding TEST_MAPPING path: %s", testMappingPath);
+ allTestMappingPaths.add(testMappingPath.resolve(TEST_MAPPING));
+ }
+ testMappingPath = testMappingPath.getParent();
+ }
+ if (!hasAdded) {
+ CLog.w("Couldn't find TEST_MAPPING files from %s", path);
+ }
+ }
+ if (allTestMappingPaths.isEmpty()) {
+ throw new RuntimeException(
+ String.format(
+ "Couldn't find TEST_MAPPING files from %s", mTestMappingRelativePaths));
+ }
+ return allTestMappingPaths;
+ }
+
+ /**
* Helper to find all tests in all TEST_MAPPING files. This is needed when a suite run requires
* to run all tests in TEST_MAPPING files for a given group, e.g., presubmit.
*
@@ -236,7 +311,12 @@
try {
Path testMappingsRootPath = Paths.get(testMappingsDir.getAbsolutePath());
Set<String> disabledTests = getDisabledTests(testMappingsRootPath, testGroup);
- stream = Files.walk(testMappingsRootPath, FileVisitOption.FOLLOW_LINKS);
+ if (mTestMappingRelativePaths.isEmpty()) {
+ stream = Files.walk(testMappingsRootPath, FileVisitOption.FOLLOW_LINKS);
+ }
+ else {
+ stream = getAllTestMappingPaths(testMappingsRootPath).stream();
+ }
stream.filter(path -> path.getFileName().toString().equals(TEST_MAPPING))
.forEach(
path ->
@@ -260,7 +340,7 @@
FileUtil.recursiveDelete(testMappingsDir);
}
- return TestMapping.mergeTests(tests);
+ return tests;
}
/**
diff --git a/tests/res/testdata/test_mapping_golden1 b/tests/res/testdata/test_mapping_golden1
new file mode 100644
index 0000000..db3998d
--- /dev/null
+++ b/tests/res/testdata/test_mapping_golden1
@@ -0,0 +1,14 @@
+{
+ "presubmit": [
+ {
+ "name": "test1",
+ "host": true,
+ "include-filter": "testClass#testMethod"
+ }
+ ],
+ "imports": [
+ {
+ "path": "path1//path2//path3"
+ }
+ ]
+}
diff --git a/tests/res/testdata/test_mapping_golden2 b/tests/res/testdata/test_mapping_golden2
new file mode 100644
index 0000000..07486c0
--- /dev/null
+++ b/tests/res/testdata/test_mapping_golden2
@@ -0,0 +1,48 @@
+{
+ "presubmit": [
+ {
+ "name": "test1",
+ "host": true
+ },
+ {
+ "name": "suite/stub1"
+ },
+ {
+ "name": "suite/stub2",
+ "keywords": ["key_1"]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "test2",
+ "options": [
+ {
+ "instrumentation-arg": "annotation=android.platform.test.annotations.Presubmit"
+ }
+ ]
+ },
+ {
+ "name": "instrument",
+ "options": [
+ {
+ "run-name": "some-name"
+ }
+ ]
+ }
+ ],
+ "othertype": [
+ {
+ "name": "test3",
+ "options": [
+ {
+ "just-an-option": ""
+ }
+ ]
+ }
+ ],
+ "imports": [
+ {
+ "path": "folder1//folder2//folder3"
+ }
+ ]
+}
diff --git a/tests/res/testdata/test_mapping_with_comments1 b/tests/res/testdata/test_mapping_with_comments1
new file mode 100644
index 0000000..3f4083f
--- /dev/null
+++ b/tests/res/testdata/test_mapping_with_comments1
@@ -0,0 +1,16 @@
+{#comments1
+ "presubmit": [//comments2 // comments3 # comment4
+ #comments3
+ { #comments4
+ "name": "test1",#comments5
+//comments6
+ "host": true,//comments7
+ "include-filter": "testClass#testMethod" #comment11 // another comments
+ }#comments8
+ ],#comments9 // another comments
+ "imports": [
+ {
+ "path": "path1//path2//path3"#comment12
+ }
+ ]
+}#comments10
diff --git a/tests/res/testdata/test_mapping_with_comments2 b/tests/res/testdata/test_mapping_with_comments2
new file mode 100644
index 0000000..da5a503
--- /dev/null
+++ b/tests/res/testdata/test_mapping_with_comments2
@@ -0,0 +1,49 @@
+{//comment..// another comment # another comment
+ "presubmit": [ #comment //another comment #// another comment
+ { # comment
+ "name": "test1",//comment
+ "host": true#comment
+ },#comment
+ {
+ "name": "suite/stub1" // comment # comment
+ },
+ {
+ "name": "suite/stub2",
+ "keywords": ["key_1"] # comment
+ } # comment
+ ], // comment
+ "postsubmit": [
+ {
+ "name": "test2",
+ "options": [
+ {
+ "instrumentation-arg": "annotation=android.platform.test.annotations.Presubmit" //comment
+ }
+ ]
+ },
+ {
+ "name": "instrument",
+ "options": [
+ {
+ "run-name": "some-name"
+ }
+ ]
+ }
+ ],
+ "othertype": [ // another comment
+ {
+ "name": "test3",
+ "options": [
+ {
+ "just-an-option": ""##comment
+ }////another comment
+ //// another comment...
+ ]
+ }
+ ],
+ "imports": [
+ {
+ "path": "folder1//folder2//folder3"//comment... # another comment // another comment
+ }
+ ]
+}
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 2b855a3..8e6606e 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -136,6 +136,7 @@
import com.android.tradefed.postprocessor.AggregatePostProcessorTest;
import com.android.tradefed.postprocessor.AveragePostProcessorTest;
import com.android.tradefed.postprocessor.BasePostProcessorTest;
+import com.android.tradefed.result.ATestFileSystemLogSaverTest;
import com.android.tradefed.result.BugreportCollectorTest;
import com.android.tradefed.result.CollectingTestListenerTest;
import com.android.tradefed.result.ConsoleResultReporterTest;
@@ -302,6 +303,7 @@
import com.android.tradefed.util.GCSFileDownloaderTest;
import com.android.tradefed.util.GoogleApiClientUtilTest;
import com.android.tradefed.util.HprofAllocSiteParserTest;
+import com.android.tradefed.util.JavaCodeCoverageFlusherTest;
import com.android.tradefed.util.JUnitXmlParserTest;
import com.android.tradefed.util.KeyguardControllerStateTest;
import com.android.tradefed.util.ListInstrumentationParserTest;
@@ -535,6 +537,7 @@
BasePostProcessorTest.class,
// result
+ ATestFileSystemLogSaverTest.class,
BugreportCollectorTest.class,
CollectingTestListenerTest.class,
ConsoleResultReporterTest.class,
@@ -742,6 +745,7 @@
GCSFileDownloaderTest.class,
GoogleApiClientUtilTest.class,
HprofAllocSiteParserTest.class,
+ JavaCodeCoverageFlusherTest.class,
JUnitXmlParserTest.class,
KeyguardControllerStateTest.class,
LegacySubprocessResultsReporterTest.class,
diff --git a/tests/src/com/android/tradefed/device/TestDeviceTest.java b/tests/src/com/android/tradefed/device/TestDeviceTest.java
index 41b76cd..81916ef 100644
--- a/tests/src/com/android/tradefed/device/TestDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/TestDeviceTest.java
@@ -2756,6 +2756,20 @@
}
/**
+ * Test that remount vendor works as expected on a device not supporting dm verity
+ *
+ * @throws Exception
+ */
+ public void testRemountVendor_verityUnsupported() throws Exception {
+ injectSystemProperty("partition.vendor.verified", "");
+ setExecuteAdbCommandExpectations(new CommandResult(CommandStatus.SUCCESS), "remount");
+ EasyMock.expect(mMockStateMonitor.waitForDeviceAvailable()).andReturn(mMockIDevice);
+ replayMocks();
+ mTestDevice.remountVendorWritable();
+ verifyMocks();
+ }
+
+ /**
* Test that remount works as expected on a device supporting dm verity v1
* @throws Exception
*/
@@ -2770,6 +2784,22 @@
mTestDevice.remountSystemWritable();
verifyMocks();
}
+ /**
+ * Test that remount vendor works as expected on a device supporting dm verity v1
+ *
+ * @throws Exception
+ */
+ public void testRemountVendor_veritySupportedV1() throws Exception {
+ injectSystemProperty("partition.vendor.verified", "1");
+ setExecuteAdbCommandExpectations(
+ new CommandResult(CommandStatus.SUCCESS), "disable-verity");
+ setRebootExpectations();
+ setExecuteAdbCommandExpectations(new CommandResult(CommandStatus.SUCCESS), "remount");
+ EasyMock.expect(mMockStateMonitor.waitForDeviceAvailable()).andReturn(mMockIDevice);
+ replayMocks();
+ mTestDevice.remountVendorWritable();
+ verifyMocks();
+ }
/**
* Test that remount works as expected on a device supporting dm verity v2
@@ -2788,6 +2818,23 @@
}
/**
+ * Test that remount vendor works as expected on a device supporting dm verity v2
+ *
+ * @throws Exception
+ */
+ public void testRemountVendor_veritySupportedV2() throws Exception {
+ injectSystemProperty("partition.vendor.verified", "2");
+ setExecuteAdbCommandExpectations(
+ new CommandResult(CommandStatus.SUCCESS), "disable-verity");
+ setRebootExpectations();
+ setExecuteAdbCommandExpectations(new CommandResult(CommandStatus.SUCCESS), "remount");
+ EasyMock.expect(mMockStateMonitor.waitForDeviceAvailable()).andReturn(mMockIDevice);
+ replayMocks();
+ mTestDevice.remountVendorWritable();
+ verifyMocks();
+ }
+
+ /**
* Test that remount works as expected on a device supporting dm verity but with unknown version
* @throws Exception
*/
@@ -2804,6 +2851,24 @@
}
/**
+ * Test that remount vendor works as expected on a device supporting dm verity but with unknown
+ * version
+ *
+ * @throws Exception
+ */
+ public void testRemountVendor_veritySupportedNonNumerical() throws Exception {
+ injectSystemProperty("partition.vendor.verified", "foo");
+ setExecuteAdbCommandExpectations(
+ new CommandResult(CommandStatus.SUCCESS), "disable-verity");
+ setRebootExpectations();
+ setExecuteAdbCommandExpectations(new CommandResult(CommandStatus.SUCCESS), "remount");
+ EasyMock.expect(mMockStateMonitor.waitForDeviceAvailable()).andReturn(mMockIDevice);
+ replayMocks();
+ mTestDevice.remountVendorWritable();
+ verifyMocks();
+ }
+
+ /**
* Test that {@link TestDevice#getBuildSigningKeys()} works for the typical "test-keys" case
* @throws Exception
*/
@@ -3473,7 +3538,7 @@
"feature:com.google.android.feature.GOOGLE_EXPERIENCE";
}
};
- assertTrue(mTestDevice.hasFeature("com.google.android.feature.EXCHANGE_6_2"));
+ assertTrue(mTestDevice.hasFeature("feature:com.google.android.feature.EXCHANGE_6_2"));
}
/**
@@ -3492,6 +3557,22 @@
}
/**
+ * Unit test for {@link TestDevice#hasFeature(String)} on partly matching case.
+ */
+ public void testHasFeature_partly_matching() throws Exception {
+ mTestDevice = new TestableTestDevice() {
+ @Override
+ public String executeShellCommand(String command) throws DeviceNotAvailableException {
+ return "feature:com.google.android.feature.EXCHANGE_6_2\n" +
+ "feature:com.google.android.feature.GOOGLE_BUILD\n" +
+ "feature:com.google.android.feature.GOOGLE_EXPERIENCE";
+ }
+ };
+ assertFalse(mTestDevice.hasFeature("feature:com.google.android.feature"));
+ assertTrue(mTestDevice.hasFeature("feature:com.google.android.feature.EXCHANGE_6_2"));
+ }
+
+ /**
* Unit test for {@link TestDevice#getSetting(int, String, String)}.
*/
public void testGetSetting() throws Exception {
@@ -3873,11 +3954,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) {
+ if (result.length != 107 && result.length != 117 && result.length != 139) {
fail(
String.format(
"Should have compress the length as expected, got %s, "
- + "expected 107 or 117",
+ + "expected 107 or 117 or 139",
result.length));
}
@@ -3885,7 +3966,13 @@
Assert.assertEquals(3000, testImage.data.length);
result = mTestDevice.compressRawImage(testImage, "JPEG", true);
// Size after compressing as JPEG
- Assert.assertEquals(1041, result.length);
+ if (result.length != 1041 && result.length != 851) {
+ fail(
+ String.format(
+ "Should have compress the length as expected, got %s, "
+ + "expected 851 or 1041",
+ result.length));
+ }
} finally {
if (testImage != null) {
testImage.data = null;
diff --git a/tests/src/com/android/tradefed/device/metric/BaseDeviceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/BaseDeviceMetricCollectorTest.java
index dd6bde5..70bc6b7 100644
--- a/tests/src/com/android/tradefed/device/metric/BaseDeviceMetricCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/BaseDeviceMetricCollectorTest.java
@@ -485,4 +485,49 @@
assertTrue(allValues.get(2).containsKey("onteststart"));
assertTrue(allValues.get(2).containsKey("ontestend"));
}
+
+ /**
+ * Test that onTestEnd with TestDescription formal supercedes the method signature without a
+ * TestDescription.
+ */
+ @Test
+ public void testOnTestEndWithTestDescription() throws Exception {
+ mBase =
+ new TwoMetricsBaseCollector() {
+ @Override
+ public void onTestEnd(
+ DeviceMetricData testData,
+ final Map<String, Metric> currentTestCaseMetrics,
+ TestDescription test) {
+ testData.addMetric(
+ test.getTestName(),
+ Metric.newBuilder()
+ .setMeasurements(
+ Measurements.newBuilder()
+ .setSingleString("value1")));
+ }
+ };
+ mBase.init(mContext, mMockListener);
+ mBase.invocationStarted(mContext);
+ mBase.testRunStarted("testRun", 1);
+ TestDescription test = new TestDescription("class", "method");
+ mBase.testStarted(test);
+ mBase.testEnded(test, new HashMap<String, Metric>());
+ mBase.testRunEnded(0L, new HashMap<String, Metric>());
+ mBase.invocationEnded(0L);
+
+ Mockito.verify(mMockListener, times(1)).invocationStarted(Mockito.any());
+ Mockito.verify(mMockListener, times(1)).testRunStarted("testRun", 1);
+ Mockito.verify(mMockListener, times(1)).testStarted(Mockito.eq(test), Mockito.anyLong());
+ Mockito.verify(mMockListener, times(1))
+ .testEnded(Mockito.eq(test), Mockito.anyLong(), mCapturedMetrics.capture());
+
+ Mockito.verify(mMockListener, times(1))
+ .testRunEnded(Mockito.anyLong(), (HashMap<String, Metric>) Mockito.any());
+
+ List<HashMap<String, Metric>> allValues = mCapturedMetrics.getAllValues();
+ assertTrue(allValues.get(0).containsKey("onteststart"));
+ assertTrue(allValues.get(0).containsKey("method"));
+ assertTrue(!allValues.get(0).containsKey("ontestend"));
+ }
}
diff --git a/tests/src/com/android/tradefed/result/ATestFileSystemLogSaverTest.java b/tests/src/com/android/tradefed/result/ATestFileSystemLogSaverTest.java
new file mode 100644
index 0000000..63de554
--- /dev/null
+++ b/tests/src/com/android/tradefed/result/ATestFileSystemLogSaverTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.result;
+
+import static org.junit.Assert.*;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+/** Unit tests for {@link ATestFileSystemLogSaver}. */
+@RunWith(JUnit4.class)
+public class ATestFileSystemLogSaverTest {
+
+ private File mReportDir;
+ private IBuildInfo mMockBuild;
+ private InvocationContext mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ mReportDir = FileUtil.createTempDir("tmpdir");
+ mMockBuild = Mockito.mock(IBuildInfo.class);
+ mContext = new InvocationContext();
+ mContext.addDeviceBuildInfo("fakeDevice", mMockBuild);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtil.recursiveDelete(mReportDir);
+ }
+
+ /** Test if generated dir in specific path. */
+ @Test
+ public void testGenerateLogReportDir() throws ConfigurationException {
+ ATestFileSystemLogSaver saver = new ATestFileSystemLogSaver();
+ OptionSetter setter = new OptionSetter(saver);
+ setter.setOptionValue("atest-log-file-path", mReportDir.getAbsolutePath());
+ saver.invocationStarted(mContext);
+ File generatedDir = new File(saver.getLogReportDir().getPath());
+ File expectedParentFolder = generatedDir.getParentFile();
+ assertEquals(expectedParentFolder.getAbsolutePath(), mReportDir.getAbsolutePath());
+ }
+}
diff --git a/tests/src/com/android/tradefed/testtype/AndroidJUnitTestTest.java b/tests/src/com/android/tradefed/testtype/AndroidJUnitTestTest.java
index b041293..85b8245 100644
--- a/tests/src/com/android/tradefed/testtype/AndroidJUnitTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/AndroidJUnitTestTest.java
@@ -99,6 +99,7 @@
};
mAndroidJUnitTest.setRunnerName(AJUR);
mAndroidJUnitTest.setPackageName(TEST_PACKAGE_VALUE);
+ mAndroidJUnitTest.setConfiguration(new Configuration("", ""));
mAndroidJUnitTest.setDevice(mMockTestDevice);
// default to no rerun, for simplicity
mAndroidJUnitTest.setRerunMode(false);
diff --git a/tests/src/com/android/tradefed/testtype/GTestBaseTest.java b/tests/src/com/android/tradefed/testtype/GTestBaseTest.java
index 27a816f..9c0036f 100644
--- a/tests/src/com/android/tradefed/testtype/GTestBaseTest.java
+++ b/tests/src/com/android/tradefed/testtype/GTestBaseTest.java
@@ -15,7 +15,6 @@
*/
package com.android.tradefed.testtype;
-import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -189,30 +188,4 @@
gTestBase.setCollectTestsOnly(true);
assertNull(gTestBase.split(5));
}
-
- /** Test that native coverage enabled will add the code coverage listener. */
- @Test
- public void testCoverage_addsCodeCoverageListener() throws ConfigurationException {
- GTestBase gTestBase = new GTestBaseImpl();
- mSetter = new OptionSetter(gTestBase);
- mSetter.setOptionValue("coverage", "true");
-
- ITestInvocationListener listener =
- gTestBase.addNativeCoverageListenerIfEnabled(mMockTestDevice, mMockListener);
-
- assertThat(listener).isInstanceOf(NativeCodeCoverageListener.class);
- }
-
- /** Test that when native coverage is disabled, the code coverage listener is not added. */
- @Test
- public void testNoCoverage_doesNotAddCodeCoverageListener() throws ConfigurationException {
- GTestBase gTestBase = new GTestBaseImpl();
- mSetter = new OptionSetter(gTestBase);
- mSetter.setOptionValue("coverage", "false");
-
- ITestInvocationListener listener =
- gTestBase.addNativeCoverageListenerIfEnabled(mMockTestDevice, mMockListener);
-
- assertThat(listener).isSameAs(mMockListener);
- }
}
diff --git a/tests/src/com/android/tradefed/testtype/GTestTest.java b/tests/src/com/android/tradefed/testtype/GTestTest.java
index 2a5e10c..07f365d 100644
--- a/tests/src/com/android/tradefed/testtype/GTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/GTestTest.java
@@ -22,12 +22,14 @@
import com.android.ddmlib.FileListingService;
import com.android.ddmlib.IShellOutputReceiver;
+import com.android.tradefed.config.Configuration;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.CollectingOutputReceiver;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.MockFileUtil;
import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.coverage.CoverageOptions;
import org.easymock.EasyMock;
import org.junit.Before;
@@ -36,6 +38,8 @@
import org.junit.runners.JUnit4;
import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -49,6 +53,10 @@
private GTest mGTest;
private OptionSetter mSetter;
+ private Configuration mConfiguration;
+ private CoverageOptions mCoverageOptions;
+ private OptionSetter mCoverageOptionsSetter;
+
/** Helper to initialize the various EasyMocks we'll need. */
@Before
public void setUp() throws Exception {
@@ -79,6 +87,14 @@
};
mGTest.setDevice(mMockITestDevice);
mSetter = new OptionSetter(mGTest);
+
+ // Set up the coverage options
+ mConfiguration = new Configuration("", "");
+ mCoverageOptions = new CoverageOptions();
+ mCoverageOptionsSetter = new OptionSetter(mCoverageOptions);
+
+ mConfiguration.setCoverageOptions(mCoverageOptions);
+ mGTest.setConfiguration(mConfiguration);
}
/**
@@ -146,7 +162,6 @@
mMockITestDevice.executeShellCommand(EasyMock.contains(test2),
EasyMock.same(mMockReceiver), EasyMock.anyLong(),
(TimeUnit)EasyMock.anyObject(), EasyMock.anyInt());
-
replayMocks();
mGTest.run(mMockInvocationListener);
@@ -414,6 +429,118 @@
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.executeShellCommand("mkdir /data/misc/trace/testcoverage"))
+ .andReturn("");
+ EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
+ EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 -1")).andReturn("");
+ 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(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.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("");
+
+ // 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(mMockInvocationListener);
+ verifyMocks();
+ }
+
@Test
public void testGetFileName() {
String expected = "bar";
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
index f00cab1..b44f2f9 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
@@ -24,6 +24,7 @@
import com.android.ddmlib.IDevice;
import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.tradefed.config.Configuration;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
@@ -83,6 +84,7 @@
return "runner";
}
};
+ mMockITest.setConfiguration(new Configuration("", ""));
mMockITest.setDevice(mMockTestDevice);
mMockITest.setPackageName(TEST_PACKAGE_VALUE);
mMockITest = Mockito.spy(mMockITest);
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationSerialTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationSerialTestTest.java
index 8c4b60e..67a2bce 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationSerialTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationSerialTestTest.java
@@ -17,6 +17,7 @@
import static org.junit.Assert.*;
+import com.android.tradefed.config.Configuration;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
@@ -72,6 +73,7 @@
};
// mock out InstrumentationTest that will be used to create InstrumentationSerialTest
+ mockITest.setConfiguration(new Configuration("", ""));
mockITest.setDevice(mMockTestDevice);
mockITest.setPackageName(packageName);
@@ -118,6 +120,7 @@
}
};
// mock out InstrumentationTest that will be used to create InstrumentationSerialTest
+ mockITest.setConfiguration(new Configuration("", ""));
mockITest.setDevice(mMockTestDevice);
mockITest.setPackageName(packageName);
mInstrumentationSerialTest = new InstrumentationSerialTest(mockITest, testList) {
@@ -190,6 +193,7 @@
};
// mock out InstrumentationTest that will be used to create InstrumentationSerialTest
+ mockITest.setConfiguration(new Configuration("", ""));
mockITest.setDevice(mMockTestDevice);
mockITest.setPackageName(packageName);
// A test-package was specified for the original instrumentation.
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
index 157683f..fafc5c4 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
@@ -38,7 +38,9 @@
import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.InstrumentationResultParser;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.tradefed.config.Configuration;
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;
@@ -51,6 +53,7 @@
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.testtype.coverage.CoverageOptions;
import com.android.tradefed.testtype.suite.GranularRetriableTestWrapperTest.CalledMetricCollector;
import com.android.tradefed.util.ListInstrumentationParser;
import com.android.tradefed.util.ListInstrumentationParser.InstrumentationTarget;
@@ -94,6 +97,11 @@
/** The {@link InstrumentationTest} under test, with all dependencies mocked out */
private InstrumentationTest mInstrumentationTest;
+ // The configuration objects.
+ private IConfiguration mConfig = null;
+ private CoverageOptions mCoverageOptions = null;
+ private OptionSetter mCoverageOptionsSetter = null;
+
// The mock objects.
@Mock IDevice mMockIDevice;
@Mock ITestDevice mMockTestDevice;
@@ -120,7 +128,7 @@
}
@Before
- public void setUp() {
+ public void setUp() throws ConfigurationException {
MockitoAnnotations.initMocks(this);
doReturn(mMockIDevice).when(mMockTestDevice).getIDevice();
@@ -139,6 +147,14 @@
mInstrumentationTest.setDevice(mMockTestDevice);
mInstrumentationTest.setListInstrumentationParser(mMockListInstrumentationParser);
mInstrumentationTest.setReRunUsingTestFile(false);
+
+ // Set up configuration.
+ mConfig = new Configuration("", "");
+ mCoverageOptions = new CoverageOptions();
+ mCoverageOptionsSetter = new OptionSetter(mCoverageOptions);
+
+ mConfig.setCoverageOptions(mCoverageOptions);
+ mInstrumentationTest.setConfiguration(mConfig);
}
/** Test normal run scenario. */
@@ -433,7 +449,7 @@
@Test
public void testRun_rerunCoverage() throws ConfigurationException, DeviceNotAvailableException {
mInstrumentationTest.setRerunMode(true);
- mInstrumentationTest.setCoverage(true);
+ mCoverageOptionsSetter.setOptionValue("coverage", "true");
Collection<TestDescription> expectedTests = ImmutableList.of(TEST1, TEST2);
@@ -509,10 +525,12 @@
/** Verify that all tests are re-run when there is a failure during a coverage run. */
@Test
- public void testRun_mergedCoverage() throws DeviceNotAvailableException {
+ public void testRun_mergedCoverage()
+ throws ConfigurationException, DeviceNotAvailableException {
mInstrumentationTest.setRerunMode(true);
- mInstrumentationTest.setCoverage(true);
mInstrumentationTest.setMergeCoverageMeasurements(true);
+ mCoverageOptionsSetter.setOptionValue("coverage", "true");
+ mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "JACOCO");
// Mock collected tests
RunInstrumentationTestsAnswer runTests =
@@ -919,8 +937,10 @@
}
@Test
- public void testAddCoverageListener_enabled() {
- mInstrumentationTest.setCoverage(true);
+ 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);
@@ -931,8 +951,8 @@
}
@Test
- public void testAddCoverageListener_disabled() {
- mInstrumentationTest.setCoverage(false);
+ public void testAddCoverageListener_disabled() throws ConfigurationException {
+ mCoverageOptionsSetter.setOptionValue("coverage", "false");
ITestInvocationListener listener =
mInstrumentationTest.addJavaCoverageListenerIfEnabled(mMockListener);
diff --git a/tests/src/com/android/tradefed/testtype/JavaCodeCoverageListenerTest.java b/tests/src/com/android/tradefed/testtype/JavaCodeCoverageListenerTest.java
index 68f7a3b..58aef9b 100644
--- a/tests/src/com/android/tradefed/testtype/JavaCodeCoverageListenerTest.java
+++ b/tests/src/com/android/tradefed/testtype/JavaCodeCoverageListenerTest.java
@@ -28,15 +28,20 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.OptionSetter;
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.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.JavaCodeCoverageFlusher;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableList;
import com.google.protobuf.ByteString;
import org.jacoco.core.tools.ExecFileLoader;
@@ -63,6 +68,7 @@
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/** Unit tests for {@link JavaCodeCoverageListener}. */
@@ -82,17 +88,25 @@
@Rule public TemporaryFolder folder = new TemporaryFolder();
@Mock ITestDevice mMockDevice;
+ @Mock JavaCodeCoverageFlusher mMockFlusher;
@Spy LogFileReader mFakeListener = new LogFileReader();
/** Object under test. */
JavaCodeCoverageListener mCodeCoverageListener;
+ CoverageOptions mCoverageOptions = null;
+ OptionSetter mCoverageOptionsSetter = null;
+
@Before
- public void setUp() {
+ public void setUp() throws ConfigurationException {
MockitoAnnotations.initMocks(this);
- mCodeCoverageListener = new JavaCodeCoverageListener(mMockDevice, false, mFakeListener);
+ mCoverageOptions = new CoverageOptions();
+ mCoverageOptionsSetter = new OptionSetter(mCoverageOptions);
+
+ mCodeCoverageListener =
+ new JavaCodeCoverageListener(mMockDevice, mCoverageOptions, false, mFakeListener);
}
@Test
@@ -170,7 +184,8 @@
measurement.writeTo(out);
}
- mCodeCoverageListener = new JavaCodeCoverageListener(mMockDevice, true, mFakeListener);
+ mCodeCoverageListener =
+ new JavaCodeCoverageListener(mMockDevice, mCoverageOptions, true, mFakeListener);
Map<String, String> metric = new HashMap<>();
metric.put("coverageFilePath", DEVICE_PATH);
@@ -207,6 +222,42 @@
.isEqualTo(partiallyCovered);
}
+ @Test
+ public void testCoverageFlush_producesMultipleMeasurements() throws Exception {
+ List<String> coverageFileList =
+ ImmutableList.of(
+ "/data/misc/trace/com.android.test1.ec",
+ "/data/misc/trace/com.android.test2.ec",
+ "/data/misc/trace/com.google.test3.ec");
+
+ mCoverageOptionsSetter.setOptionValue("coverage-flush", "true");
+
+ // Setup mocks.
+ File coverageFile = folder.newFile("coverage.ec");
+ try (OutputStream out = new FileOutputStream(coverageFile)) {
+ COVERAGE_MEASUREMENT.writeTo(out);
+ }
+ doReturn(coverageFile).when(mMockDevice).pullFile(DEVICE_PATH);
+
+ for (String additionalFile : coverageFileList) {
+ File coverage = folder.newFile();
+ try (OutputStream out = new FileOutputStream(coverage)) {
+ COVERAGE_MEASUREMENT.writeTo(out);
+ }
+ doReturn(coverage).when(mMockDevice).pullFile(additionalFile);
+ }
+
+ doReturn(coverageFileList).when(mMockFlusher).forceCoverageFlush();
+
+ mCodeCoverageListener.setCoverageFlusher(mMockFlusher);
+
+ // Simulate a test run.
+ mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
+ Map<String, String> metric = new HashMap<>();
+ metric.put("coverageFilePath", DEVICE_PATH);
+ mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
+ }
+
private static <T> String vmName(Class<T> clazz) {
return clazz.getName().replace('.', '/');
}
diff --git a/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java b/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java
index 98d88eb..a0c7f18 100644
--- a/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java
+++ b/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java
@@ -19,21 +19,27 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
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.android.tradefed.util.TarUtil;
import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableMap;
import com.google.protobuf.ByteString;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -49,7 +55,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
@@ -58,7 +63,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.StringJoiner;
import java.util.zip.ZipFile;
/** Unit tests for {@link NativeCodeCoverageListener}. */
@@ -69,48 +73,40 @@
private static final int TEST_COUNT = 5;
private static final long ELAPSED_TIME = 1000;
- private static final ByteString COVERAGE_MEASUREMENT =
- ByteString.copyFromUtf8("Mi estas kovrado mezurado");
-
@Rule public TemporaryFolder folder = new TemporaryFolder();
@Mock ITestDevice mMockDevice;
LogFileReader mFakeListener = new LogFileReader();
+ /** Options for coverage. */
+ CoverageOptions mCoverageOptions = null;
+
+ OptionSetter mCoverageOptionsSetter = null;
+
/** Object under test. */
NativeCodeCoverageListener mCodeCoverageListener;
@Before
- public void setUp() {
+ public void setUp() throws ConfigurationException {
MockitoAnnotations.initMocks(this);
-
mCodeCoverageListener = new NativeCodeCoverageListener(mMockDevice, mFakeListener);
+ mCoverageOptions = new CoverageOptions();
+ mCoverageOptionsSetter = new OptionSetter(mCoverageOptions);
}
@Test
public void test_logsCoverageZip() throws DeviceNotAvailableException, IOException {
// Setup mocks to write the coverage measurement to the file.
doReturn(true).when(mMockDevice).enableAdbRoot();
- doReturn(
- new StringJoiner("\n")
- .add("/data/misc/trace/proc/self/cwd/out/path/to/coverage.gcda")
- .add(
- "/data/misc/trace/proc/self/cwd/out/path/to/.hidden/coverage2.gcda")
- .toString())
- .when(mMockDevice)
- .executeShellCommand(anyString());
- doAnswer(
- inv -> {
- File destFile = (File) inv.getArgument(1);
- try (OutputStream out = new FileOutputStream(destFile)) {
- // Write the filename as the contents.
- out.write(destFile.getName().getBytes(StandardCharsets.UTF_8));
- }
- return true;
- })
- .when(mMockDevice)
- .pullFile(anyString(), any());
+ File tarGz =
+ createTarGz(
+ ImmutableMap.of(
+ "path/to/coverage.gcda",
+ ByteString.copyFromUtf8("coverage.gcda"),
+ "path/to/.hidden/coverage2.gcda",
+ ByteString.copyFromUtf8("coverage2.gcda")));
+ doReturn(tarGz).when(mMockDevice).pullFile(anyString());
// Simulate a test run.
mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
@@ -140,7 +136,7 @@
@Test
public void testNoCoverageFiles_logsEmptyZip() throws DeviceNotAvailableException, IOException {
doReturn(true).when(mMockDevice).enableAdbRoot();
- doReturn("").when(mMockDevice).executeShellCommand(anyString());
+ doReturn(createTarGz(ImmutableMap.of())).when(mMockDevice).pullFile(anyString());
// Simulate a test run.
mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
@@ -160,14 +156,55 @@
}
@Test
+ public void testCoverageFlushAllProcesses_flushAllCommandCalled()
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ mCoverageOptionsSetter.setOptionValue("coverage-flush", "true");
+
+ mCodeCoverageListener =
+ new NativeCodeCoverageListener(mMockDevice, mCoverageOptions, mFakeListener);
+
+ doReturn(true).when(mMockDevice).enableAdbRoot();
+ doReturn(true).when(mMockDevice).isAdbRoot();
+ doReturn(createTarGz(ImmutableMap.of())).when(mMockDevice).pullFile(anyString());
+
+ // Simulate a test run.
+ 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");
+ }
+
+ @Test
+ public void testCoverageFlushSpecificProcesses_flushCommandCalled()
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ 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(createTarGz(ImmutableMap.of())).when(mMockDevice).pullFile(anyString());
+
+ // Simulate a test run.
+ 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");
+ }
+
+ @Test
public void testFailure_unableToPullFile() throws DeviceNotAvailableException {
// Setup mocks.
doReturn(true).when(mMockDevice).enableAdbRoot();
- doReturn("/data/misc/trace/proc/self/cwd/out/some/path/to/coverage.gcda\n")
- .when(mMockDevice)
- .executeShellCommand(anyString());
- doReturn(false).when(mMockDevice).pullFile(anyString(), any());
-
// Simulate a test run.
mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
@@ -184,6 +221,46 @@
assertThat(mFakeListener.getLogs()).isEmpty();
}
+ @Test
+ public void testNoCollectOnTestEnd_noCoverageMeasurements() throws Exception {
+ mCodeCoverageListener = new NativeCodeCoverageListener(mMockDevice, mFakeListener);
+ mCodeCoverageListener.setCollectOnTestEnd(false);
+
+ // Simute a test run.
+ mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
+ Map<String, String> metric = new HashMap<>();
+ mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
+
+ // Verify nothing was logged.
+ assertThat(mFakeListener.getLogs()).isEmpty();
+
+ // Setup mocks to write the coverage measurement to the file.
+ doReturn(true).when(mMockDevice).enableAdbRoot();
+ File tarGz =
+ createTarGz(
+ ImmutableMap.of(
+ "path/to/coverage.gcda", ByteString.copyFromUtf8("coverage.gcda")));
+ doReturn(tarGz).when(mMockDevice).pullFile(anyString());
+
+ // Manually call logCoverageMeasurements().
+ mCodeCoverageListener.logCoverageMeasurements("manual");
+
+ // Verify testLog(..) was called with the coverage file in a zip.
+ List<ByteString> logs = mFakeListener.getLogs();
+ assertThat(logs).hasSize(1);
+ File outputZip = folder.newFile("coverage.zip");
+ try (OutputStream out = new FileOutputStream(outputZip)) {
+ logs.get(0).writeTo(out);
+ }
+
+ URI uri = URI.create(String.format("jar:file:%s", outputZip));
+ try (FileSystem filesystem = FileSystems.newFileSystem(uri, new HashMap<>())) {
+ Path path = filesystem.getPath("/path/to/coverage.gcda");
+ assertThat(ByteString.readFrom(Files.newInputStream(path)))
+ .isEqualTo(ByteString.copyFromUtf8("coverage.gcda"));
+ }
+ }
+
/** An {@link ITestInvocationListener} which reads test log data streams for verification. */
private static class LogFileReader implements ITestInvocationListener {
private List<ByteString> mLogs = new ArrayList<>();
@@ -202,4 +279,21 @@
return new ArrayList<>(mLogs);
}
}
+
+ /** Utility method to create .tar.gz files. */
+ private File createTarGz(Map<String, ByteString> fileContents) throws IOException {
+ File tarFile = folder.newFile("coverage.tar");
+ try (TarArchiveOutputStream out =
+ new TarArchiveOutputStream(new FileOutputStream(tarFile))) {
+ for (Map.Entry<String, ByteString> file : fileContents.entrySet()) {
+ TarArchiveEntry entry = new TarArchiveEntry(file.getKey());
+ entry.setSize(file.getValue().size());
+
+ out.putArchiveEntry(entry);
+ file.getValue().writeTo(out);
+ out.closeArchiveEntry();
+ }
+ }
+ return TarUtil.gzip(tarFile);
+ }
}
diff --git a/tests/src/com/android/tradefed/testtype/suite/AtestRunnerTest.java b/tests/src/com/android/tradefed/testtype/suite/AtestRunnerTest.java
index 9b74784..45f5a4d 100644
--- a/tests/src/com/android/tradefed/testtype/suite/AtestRunnerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/AtestRunnerTest.java
@@ -34,12 +34,14 @@
import com.android.tradefed.testtype.InstrumentationTest;
import com.android.tradefed.testtype.UiAutomatorTest;
import com.android.tradefed.util.AbiUtils;
+import com.android.tradefed.util.FileUtil;
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.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -50,6 +52,12 @@
@RunWith(JUnit4.class)
public class AtestRunnerTest {
+ private static final String TEST_CONFIG =
+ "<configuration description=\"Runs a stub tests part of some suite\">\n"
+ + " <test class=\"com.android.tradefed.testtype.suite.SuiteModuleLoaderTest"
+ + "$TestInject\" />\n"
+ + "</configuration>";
+
private static final String ABI = "armeabi-v7a";
private static final String TEST_NAME_FMT = ABI + " %s";
private static final String INSTRUMENTATION_TEST_NAME =
@@ -123,6 +131,55 @@
}
@Test
+ public void testLoadTests_WithTFConfigSpecified() throws Exception {
+ setter = new OptionSetter(mSpyRunner);
+ setter.setOptionValue("suite-config-prefix", "suite");
+ setter.setOptionValue("tf-config-path", "suite/base-suite1");
+ LinkedHashMap<String, IConfiguration> configMap = mSpyRunner.loadTests();
+ assertEquals(1, configMap.size());
+ String testName = String.format(TEST_NAME_FMT, "suite/base-suite1");
+ assertTrue(configMap.containsKey(testName));
+ }
+
+ @Test
+ public void testLoadTests_WithModuleAndTFConfigSpecified() throws Exception {
+ File tmpDir = FileUtil.createTempDir("some-dir");
+ String filePath = createModuleConfig(tmpDir, "TestModule");
+ try {
+ mSpyRunner.setupFilters(tmpDir);
+ setter = new OptionSetter(mSpyRunner);
+ setter.setOptionValue("suite-config-prefix", "suite");
+ setter.setOptionValue("tf-config-path", "suite/base-suite1");
+ setter.setOptionValue("module-config-path", filePath);
+ LinkedHashMap<String, IConfiguration> configMap = mSpyRunner.loadTests();
+ assertEquals(2, configMap.size());
+ String testName = String.format(TEST_NAME_FMT, "TestModule");
+ assertTrue(configMap.containsKey(testName));
+ testName = String.format(TEST_NAME_FMT, "suite/base-suite1");
+ assertTrue(configMap.containsKey(testName));
+ } finally {
+ FileUtil.recursiveDelete(tmpDir);
+ }
+ }
+
+ @Test
+ public void testLoadTests_WithModuleConfigSpecified() throws Exception {
+ File tmpDir = FileUtil.createTempDir("some-dir");
+ String filePath = createModuleConfig(tmpDir, "TestModule");
+ try {
+ mSpyRunner.setupFilters(tmpDir);
+ setter = new OptionSetter(mSpyRunner);
+ setter.setOptionValue("module-config-path", filePath);
+ LinkedHashMap<String, IConfiguration> configMap = mSpyRunner.loadTests();
+ assertEquals(1, configMap.size());
+ String testName = String.format(TEST_NAME_FMT, "TestModule");
+ assertTrue(configMap.containsKey(testName));
+ } finally {
+ FileUtil.recursiveDelete(tmpDir);
+ }
+ }
+
+ @Test
public void testLoadTests_ignoreFilter() throws Exception {
setter = new OptionSetter(mSpyRunner);
setter.setOptionValue("suite-config-prefix", "suite");
@@ -212,4 +269,10 @@
List<ITestInvocationListener> listeners = mSpyRunner.createModuleListeners();
assertEquals(1, listeners.size());
}
+
+ private String createModuleConfig(File dir, String moduleName) throws IOException {
+ File moduleConfig = new File(dir, moduleName + SuiteModuleLoader.CONFIG_EXT);
+ FileUtil.writeToFile(TEST_CONFIG, moduleConfig);
+ return moduleConfig.getAbsolutePath();
+ }
}
diff --git a/tests/src/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java
index df7bfd2..fb7647f 100644
--- a/tests/src/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java
@@ -133,6 +133,41 @@
}
}
+ /**
+ * Test that we create a module and the parameterized version of it for the include filter if
+ * not explicitly excluded.
+ */
+ @Test
+ public void testSetupFilters_parameterized_filter() throws Exception {
+ File tmpDir = FileUtil.createTempDir(TEST_MODULE);
+ File moduleConfig = new File(tmpDir, "CtsGestureTestCases.config");
+ moduleConfig.createNewFile();
+ try {
+ OptionSetter setter = new OptionSetter(mRunner);
+ setter.setOptionValue("enable-parameterized-modules", "true");
+ // The Gesture module has a parameter "instant".
+ setter.setOptionValue("module", "Gesture");
+ mRunner.setupFilters(tmpDir);
+ assertEquals(2, mRunner.getIncludeFilter().size());
+ assertThat(
+ mRunner.getIncludeFilter(),
+ hasItem(
+ new SuiteTestFilter(
+ mRunner.getRequestedAbi(), "CtsGestureTestCases", null)
+ .toString()));
+ assertThat(
+ mRunner.getIncludeFilter(),
+ hasItem(
+ new SuiteTestFilter(
+ mRunner.getRequestedAbi(),
+ "CtsGestureTestCases[instant]",
+ null)
+ .toString()));
+ } finally {
+ FileUtil.recursiveDelete(tmpDir);
+ }
+ }
+
@Test
public void testSetupFilters_match() throws Exception {
File tmpDir = FileUtil.createTempDir(TEST_MODULE);
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index 8374a80..0fb2bb1 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -29,6 +30,7 @@
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.ConfigurationFactory;
import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -170,7 +172,10 @@
}
}
- public static class StubCollectingTest implements IRemoteTest, ITestFilterReceiver {
+ public static class StubCollectingTest
+ implements IRemoteTest, IConfigurationReceiver, ITestFilterReceiver {
+ private IConfiguration mConfiguration;
+
private DeviceNotAvailableException mException;
private RuntimeException mRunException;
private String mFailed;
@@ -189,6 +194,10 @@
mFailed = errMessage;
}
+ public IConfiguration getConfiguration() {
+ return mConfiguration;
+ }
+
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
listener.testRunStarted(TEST_CONFIG_NAME, 1);
@@ -211,6 +220,11 @@
}
@Override
+ public void setConfiguration(IConfiguration config) {
+ mConfiguration = config;
+ }
+
+ @Override
public void addIncludeFilter(String filter) {
// ignored
}
@@ -1442,6 +1456,64 @@
EasyMock.verify(moduleListener);
}
+ /**
+ * Test that {@link CoverageOptions} are passed from the main configuration to the module
+ * configuration.
+ */
+ @Test
+ public void testRun_coverageOptionsCopied() throws Exception {
+ ITestInvocationListener moduleListener = EasyMock.createMock(ITestInvocationListener.class);
+ StubCollectingTest test = new StubCollectingTest();
+ mTestSuite =
+ new TestSuiteImpl() {
+ @Override
+ public LinkedHashMap<String, IConfiguration> loadTests() {
+ LinkedHashMap<String, IConfiguration> testConfig = new LinkedHashMap<>();
+ try {
+ IConfiguration fake =
+ ConfigurationFactory.getInstance()
+ .createConfigurationFromArgs(
+ new String[] {EMPTY_CONFIG});
+ fake.setTest(test);
+ testConfig.put(TEST_CONFIG_NAME, fake);
+ } catch (ConfigurationException e) {
+ CLog.e(e);
+ throw new RuntimeException(e);
+ }
+ return testConfig;
+ }
+ };
+ mTestSuite.setDevice(mMockDevice);
+ mTestSuite.setBuild(mMockBuildInfo);
+ mTestSuite.setConfiguration(mStubMainConfiguration);
+ mTestSuite.setInvocationContext(mContext);
+
+ List<ISystemStatusChecker> sysChecker = new ArrayList<ISystemStatusChecker>();
+ sysChecker.add(mMockSysChecker);
+ mTestSuite.setSystemStatusChecker(sysChecker);
+ EasyMock.expect(mMockSysChecker.preExecutionCheck(EasyMock.eq(mMockDevice)))
+ .andReturn(new StatusCheckerResult(CheckStatus.SUCCESS));
+ EasyMock.expect(mMockSysChecker.postExecutionCheck(EasyMock.eq(mMockDevice)))
+ .andReturn(new StatusCheckerResult(CheckStatus.SUCCESS));
+ mMockListener.testModuleStarted(EasyMock.anyObject());
+ mMockListener.testRunStarted(
+ EasyMock.eq(TEST_CONFIG_NAME), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
+ TestDescription testDescription = new TestDescription(EMPTY_CONFIG, EMPTY_CONFIG);
+ mMockListener.testStarted(testDescription, 0);
+ mMockListener.testEnded(testDescription, 5, new HashMap<String, Metric>());
+ mMockListener.testRunEnded(
+ EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
+ mMockListener.testModuleEnded();
+ replayMocks();
+ mTestSuite.run(mMockListener);
+ verifyMocks();
+
+ // Check that CoverageOptions was copied to the module.
+ assertSame(
+ mStubMainConfiguration.getCoverageOptions(),
+ test.getConfiguration().getCoverageOptions());
+ }
+
/** Test for {@link ITestSuite#run(ITestInvocationListener)} when a module listener is used. */
@Test
public void testRun_GranularRerunwithModuleListener() throws Exception {
diff --git a/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java b/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java
index 4c7a8ba..4eabd26 100644
--- a/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/SuiteModuleLoaderTest.java
@@ -17,6 +17,7 @@
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 com.android.tradefed.config.IConfiguration;
@@ -56,6 +57,12 @@
+ "$TestInject\" />\n"
+ "</configuration>";
+ private static final String TEST_INSTANT_CONFIG =
+ "<configuration description=\"Runs a stub tests part of some suite\">\n"
+ + " <option name=\"config-descriptor:metadata\" key=\"parameter\" value=\"instant_app\" />"
+ + " <test class=\"com.android.tradefed.testtype.suite.TestSuiteStub\" />\n"
+ + "</configuration>";
+
private SuiteModuleLoader mRepo;
private File mTestsDir;
private Set<IAbi> mAbis;
@@ -83,6 +90,11 @@
FileUtil.writeToFile(TEST_CONFIG, module);
}
+ private void createInstantModuleConfig(String moduleName) throws IOException {
+ File module = new File(mTestsDir, moduleName + SuiteModuleLoader.CONFIG_EXT);
+ FileUtil.writeToFile(TEST_INSTANT_CONFIG, module);
+ }
+
@OptionClass(alias = "test-inject")
public static class TestInject implements IRemoteTest {
@Option(name = "simple-string")
@@ -215,4 +227,123 @@
TestInject checker = (TestInject) config.getTests().get(0);
assertEquals("value1", checker.testAlias);
}
+
+ /**
+ * Test that if the base module is excluded in full, the filters of parameterized modules are
+ * still populated with the proper filters.
+ */
+ @Test
+ public void testFilterParameterized() throws Exception {
+ Map<String, List<SuiteTestFilter>> excludeFilters = new LinkedHashMap<>();
+ createInstantModuleConfig("basemodule");
+ SuiteTestFilter fullFilter = SuiteTestFilter.createFrom("armeabi-v7a basemodule");
+ excludeFilters.put("armeabi-v7a basemodule", Arrays.asList(fullFilter));
+
+ SuiteTestFilter instantMethodFilter =
+ SuiteTestFilter.createFrom(
+ "armeabi-v7a basemodule[instant] NativeDnsAsyncTest#Async_Cancel");
+ excludeFilters.put("armeabi-v7a basemodule[instant]", Arrays.asList(instantMethodFilter));
+
+ mRepo =
+ new SuiteModuleLoader(
+ new LinkedHashMap<String, List<SuiteTestFilter>>(),
+ excludeFilters,
+ new ArrayList<>(),
+ new ArrayList<>());
+ mRepo.setParameterizedModules(true);
+
+ List<String> patterns = new ArrayList<>();
+ patterns.add(".*.config");
+ patterns.add(".*.xml");
+ LinkedHashMap<String, IConfiguration> res =
+ mRepo.loadConfigsFromDirectory(
+ Arrays.asList(mTestsDir), mAbis, null, null, patterns);
+ assertEquals(1, res.size());
+ // Full module was excluded completely
+ IConfiguration instantModule = res.get("armeabi-v7a basemodule[instant]");
+ assertNotNull(instantModule);
+ TestSuiteStub stubTest = (TestSuiteStub) instantModule.getTests().get(0);
+ assertEquals(1, stubTest.getExcludeFilters().size());
+ assertEquals(
+ "NativeDnsAsyncTest#Async_Cancel", stubTest.getExcludeFilters().iterator().next());
+ }
+
+ /**
+ * Test that the configuration can be found if specifying specific path.
+ */
+ @Test
+ public void testLoadConfigsFromSpecifiedPaths_OneModule() throws Exception {
+ createModuleConfig("module1");
+ File module1 = new File(mTestsDir, "module1" + SuiteModuleLoader.CONFIG_EXT);
+
+ mRepo =
+ new SuiteModuleLoader(
+ new LinkedHashMap<String, List<SuiteTestFilter>>(),
+ new LinkedHashMap<String, List<SuiteTestFilter>>(),
+ new ArrayList<>(),
+ new ArrayList<>());
+
+ LinkedHashMap<String, IConfiguration> res =
+ mRepo.loadConfigsFromSpecifiedPaths(
+ Arrays.asList(module1), mAbis, null);
+ assertEquals(1, res.size());
+ assertNotNull(res.get("armeabi-v7a module1"));
+ }
+
+ /**
+ * Test that multiple configurations can be found if specifying specific paths.
+ */
+ @Test
+ public void testLoadConfigsFromSpecifiedPaths_MultipleModules() throws Exception {
+ createModuleConfig("module1");
+ File module1 = new File(mTestsDir, "module1" + SuiteModuleLoader.CONFIG_EXT);
+ createModuleConfig("module2");
+ File module2 = new File(mTestsDir, "module2" + SuiteModuleLoader.CONFIG_EXT);
+
+ mRepo =
+ new SuiteModuleLoader(
+ new LinkedHashMap<String, List<SuiteTestFilter>>(),
+ new LinkedHashMap<String, List<SuiteTestFilter>>(),
+ new ArrayList<>(),
+ new ArrayList<>());
+
+ LinkedHashMap<String, IConfiguration> res =
+ mRepo.loadConfigsFromSpecifiedPaths(
+ Arrays.asList(module1, module2), mAbis, null);
+ assertEquals(2, res.size());
+ assertNotNull(res.get("armeabi-v7a module1"));
+ assertNotNull(res.get("armeabi-v7a module2"));
+ }
+
+ /**
+ * Test that configuration can be found correctly if specifying specific paths but someone is
+ * excluded.
+ */
+ @Test
+ public void testLoadConfigsFromSpecifiedPaths_WithExcludeFilter() throws Exception {
+ createModuleConfig("module1");
+ File module1 = new File(mTestsDir, "module1" + SuiteModuleLoader.CONFIG_EXT);
+ createModuleConfig("module2");
+ File module2 = new File(mTestsDir, "module2" + SuiteModuleLoader.CONFIG_EXT);
+
+ Map<String, List<SuiteTestFilter>> excludeFilters = new LinkedHashMap<>();
+ SuiteTestFilter filter =
+ SuiteTestFilter.createFrom(
+ "armeabi-v7a module2");
+ excludeFilters.put("armeabi-v7a module2", Arrays.asList(filter));
+
+ mRepo =
+ new SuiteModuleLoader(
+ new LinkedHashMap<String, List<SuiteTestFilter>>(),
+ excludeFilters,
+ new ArrayList<>(),
+ new ArrayList<>());
+
+ LinkedHashMap<String, IConfiguration> res =
+ mRepo.loadConfigsFromSpecifiedPaths(
+ Arrays.asList(module1, module2), mAbis, null);
+ assertEquals(1, res.size());
+ assertNotNull(res.get("armeabi-v7a module1"));
+ assertNull(res.get("armeabi-v7a module2"));
+ }
}
diff --git a/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java b/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
index bbf22c4..03f39c2 100644
--- a/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
@@ -20,6 +20,8 @@
import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.ConfigurationFactory;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -27,12 +29,15 @@
import com.android.tradefed.testtype.Abi;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IRemoteTest;
-import com.android.tradefed.testtype.InstrumentationTest;
+import com.android.tradefed.testtype.StubTest;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.ZipUtil;
+import com.android.tradefed.util.testmapping.TestInfo;
import com.android.tradefed.util.testmapping.TestMapping;
+import com.android.tradefed.util.testmapping.TestOption;
+import java.util.ArrayList;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
@@ -57,13 +62,17 @@
private static final String ABI_1 = "arm64-v8a";
private static final String ABI_2 = "armeabi-v7a";
+ private static final String DISABLED_PRESUBMIT_TESTS = "disabled-presubmit-tests";
+ private static final String EMPTY_CONFIG = "empty";
private static final String NON_EXISTING_DIR = "non-existing-dir";
+ private static final String TEST_CONFIG_NAME = "test";
private static final String TEST_DATA_DIR = "testdata";
private static final String TEST_MAPPING = "TEST_MAPPING";
private static final String TEST_MAPPINGS_ZIP = "test_mappings.zip";
- private static final String DISABLED_PRESUBMIT_TESTS = "disabled-presubmit-tests";
private TestMappingSuiteRunner mRunner;
+ private OptionSetter mOptionSetter;
+ private TestMappingSuiteRunner mRunner2;
private IDeviceBuildInfo mBuildInfo;
private ITestDevice mMockDevice;
@@ -75,8 +84,16 @@
mRunner.setBuild(mBuildInfo);
mRunner.setDevice(mMockDevice);
- EasyMock.expect(mBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).andReturn(null);
- EasyMock.expect(mBuildInfo.getTestsDir()).andReturn(new File(NON_EXISTING_DIR));
+ mOptionSetter = new OptionSetter(mRunner);
+ mOptionSetter.setOptionValue("suite-config-prefix", "suite");
+
+ mRunner2 = new FakeTestMappingSuiteRunner();
+ mRunner2.setBuild(mBuildInfo);
+ mRunner2.setDevice(mMockDevice);
+
+ EasyMock.expect(mBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).andReturn(null)
+ .anyTimes();
+ EasyMock.expect(mBuildInfo.getTestsDir()).andReturn(new File(NON_EXISTING_DIR)).anyTimes();
EasyMock.expect(mMockDevice.getProperty(EasyMock.anyObject())).andReturn(ABI_1);
EasyMock.expect(mMockDevice.getProperty(EasyMock.anyObject())).andReturn(ABI_2);
EasyMock.replay(mBuildInfo, mMockDevice);
@@ -94,6 +111,43 @@
abis.add(new Abi(ABI_2, AbiUtils.getBitness(ABI_2)));
return abis;
}
+
+ @Override
+ List<IRemoteTest> createIndividualTests(Set<TestInfo> testInfos, String configPath) {
+ IRemoteTest fakeTest = EasyMock.createMock(IRemoteTest.class);
+ return new ArrayList<>(Arrays.asList(fakeTest));
+ }
+ }
+
+ /**
+ * Test TestMappingSuiteRunner that create a fake IConfiguration with fake a test object.
+ */
+ public static class FakeTestMappingSuiteRunner extends TestMappingSuiteRunner {
+ @Override
+ public Set<IAbi> getAbis(ITestDevice device) throws DeviceNotAvailableException {
+ Set<IAbi> abis = new HashSet<>();
+ abis.add(new Abi(ABI_1, AbiUtils.getBitness(ABI_1)));
+ abis.add(new Abi(ABI_2, AbiUtils.getBitness(ABI_2)));
+ return abis;
+ }
+
+ @Override
+ public LinkedHashMap<String, IConfiguration> loadingStrategy(Set<IAbi> abis,
+ List<File> testsDirs, String suitePrefix, String suiteTag) {
+ LinkedHashMap<String, IConfiguration> testConfig = new LinkedHashMap<>();
+ try {
+ IConfiguration config =
+ ConfigurationFactory.getInstance()
+ .createConfigurationFromArgs(new String[] {EMPTY_CONFIG});
+ config.setTest(new StubTest());
+ config.getConfigurationDescription().setModuleName(TEST_CONFIG_NAME);
+ testConfig.put(TEST_CONFIG_NAME, config);
+
+ } catch (ConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+ return testConfig;
+ }
}
/**
@@ -108,6 +162,17 @@
mRunner.loadTests();
}
+ /**
+ * Test for {@link TestMappingSuiteRunner#loadTests()} to fail when both options include-filter
+ * and test-mapping-path are set.
+ */
+ @Test(expected = RuntimeException.class)
+ public void testLoadTests_conflictOptions() throws Exception {
+ mOptionSetter.setOptionValue("include-filter", "test1");
+ mOptionSetter.setOptionValue("test-mapping-path", "path1");
+ mRunner.loadTests();
+ }
+
/** Test for {@link TestMappingSuiteRunner#loadTests()} to fail when no test option is set. */
@Test(expected = RuntimeException.class)
public void testLoadTests_noOption() throws Exception {
@@ -173,14 +238,7 @@
assertTrue(mRunner.getIncludeFilter().contains("test2"));
assertTrue(mRunner.getIncludeFilter().contains("instrument"));
assertTrue(mRunner.getIncludeFilter().contains("suite/stub1"));
- // Filters are applied directly
- assertTrue(mRunner.getExcludeFilter().contains("suite/stub1 filter.com"));
- assertTrue(mRunner.getIncludeFilter().contains("suite/stub2 filter.com"));
-
- // Check module-arg work as expected.
- InstrumentationTest test =
- (InstrumentationTest) configMap.get("arm64-v8a instrument").getTests().get(0);
- assertEquals("some-name", test.getRunName());
+ assertTrue(mRunner.getIncludeFilter().contains("suite/stub2"));
assertEquals(6, configMap.size());
assertTrue(configMap.containsKey(ABI_1 + " instrument"));
@@ -478,4 +536,248 @@
assertTrue(configMap.containsKey(ABI_2 + " suite/stubAbi"));
EasyMock.verify(mockDevice);
}
+
+ /**
+ * Test for {@link TestMappingSuiteRunner#loadTests()} that when force-test-mapping-module is
+ * specified, tests would be filtered.
+ */
+ @Test
+ public void testLoadTestsWithModule() throws Exception {
+ File tempDir = null;
+ try {
+ OptionSetter setter = new OptionSetter(mRunner);
+ setter.setOptionValue("test-mapping-test-group", "postsubmit");
+ setter.setOptionValue("force-test-mapping-module", "suite/stub1");
+
+ tempDir = FileUtil.createTempDir("test_mapping");
+
+ File srcDir = FileUtil.createTempDir("src", tempDir);
+ String srcFile =
+ File.separator + TEST_DATA_DIR + File.separator + DISABLED_PRESUBMIT_TESTS;
+ InputStream resourceStream = this.getClass().getResourceAsStream(srcFile);
+ FileUtil.saveResourceFile(resourceStream, srcDir, DISABLED_PRESUBMIT_TESTS);
+
+ srcFile = File.separator + TEST_DATA_DIR + File.separator + "test_mapping_1";
+ resourceStream = this.getClass().getResourceAsStream(srcFile);
+ FileUtil.saveResourceFile(resourceStream, srcDir, TEST_MAPPING);
+ File subDir = FileUtil.createTempDir("sub_dir", srcDir);
+ srcFile = File.separator + TEST_DATA_DIR + File.separator + "test_mapping_2";
+ resourceStream = this.getClass().getResourceAsStream(srcFile);
+ FileUtil.saveResourceFile(resourceStream, subDir, TEST_MAPPING);
+
+ List<File> filesToZip =
+ Arrays.asList(srcDir, new File(tempDir, DISABLED_PRESUBMIT_TESTS));
+ File zipFile = Paths.get(tempDir.getAbsolutePath(), TEST_MAPPINGS_ZIP).toFile();
+ ZipUtil.createZip(filesToZip, zipFile);
+
+ IDeviceBuildInfo mockBuildInfo = EasyMock.createMock(IDeviceBuildInfo.class);
+ EasyMock.expect(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR))
+ .andReturn(null);
+ EasyMock.expect(mockBuildInfo.getTestsDir()).andReturn(new File("non-existing-dir"));
+ EasyMock.expect(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).andReturn(zipFile);
+
+ mRunner.setBuild(mockBuildInfo);
+ EasyMock.replay(mockBuildInfo);
+
+ LinkedHashMap<String, IConfiguration> configMap = mRunner.loadTests();
+ assertEquals(2, configMap.size());
+ assertTrue(configMap.containsKey(ABI_1 + " suite/stub1"));
+ assertTrue(configMap.containsKey(ABI_2 + " suite/stub1"));
+ EasyMock.verify(mockBuildInfo);
+ } finally {
+ FileUtil.recursiveDelete(tempDir);
+ }
+ }
+
+ /**
+ * Test for {@link TestMappingSuiteRunner#loadTests()} that when multi force-test-mapping-module
+ * are specified, tests would be filtered.
+ */
+ @Test
+ public void testLoadTestsWithMultiModules() throws Exception {
+ File tempDir = null;
+ try {
+ OptionSetter setter = new OptionSetter(mRunner);
+ setter.setOptionValue("test-mapping-test-group", "postsubmit");
+ setter.setOptionValue("force-test-mapping-module", "suite/stub1");
+ setter.setOptionValue("force-test-mapping-module", "suite/stub2");
+
+ tempDir = FileUtil.createTempDir("test_mapping");
+
+ File srcDir = FileUtil.createTempDir("src", tempDir);
+ String srcFile =
+ File.separator + TEST_DATA_DIR + File.separator + DISABLED_PRESUBMIT_TESTS;
+ InputStream resourceStream = this.getClass().getResourceAsStream(srcFile);
+ FileUtil.saveResourceFile(resourceStream, srcDir, DISABLED_PRESUBMIT_TESTS);
+
+ srcFile = File.separator + TEST_DATA_DIR + File.separator + "test_mapping_1";
+ resourceStream = this.getClass().getResourceAsStream(srcFile);
+ FileUtil.saveResourceFile(resourceStream, srcDir, TEST_MAPPING);
+ File subDir = FileUtil.createTempDir("sub_dir", srcDir);
+ srcFile = File.separator + TEST_DATA_DIR + File.separator + "test_mapping_2";
+ resourceStream = this.getClass().getResourceAsStream(srcFile);
+ FileUtil.saveResourceFile(resourceStream, subDir, TEST_MAPPING);
+
+ List<File> filesToZip =
+ Arrays.asList(srcDir, new File(tempDir, DISABLED_PRESUBMIT_TESTS));
+ File zipFile = Paths.get(tempDir.getAbsolutePath(), TEST_MAPPINGS_ZIP).toFile();
+ ZipUtil.createZip(filesToZip, zipFile);
+
+ IDeviceBuildInfo mockBuildInfo = EasyMock.createMock(IDeviceBuildInfo.class);
+ EasyMock.expect(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR))
+ .andReturn(null);
+ EasyMock.expect(mockBuildInfo.getTestsDir()).andReturn(new File("non-existing-dir"));
+ EasyMock.expect(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).andReturn(zipFile);
+
+ mRunner.setBuild(mockBuildInfo);
+ EasyMock.replay(mockBuildInfo);
+
+ LinkedHashMap<String, IConfiguration> configMap = mRunner.loadTests();
+ assertEquals(4, configMap.size());
+ assertTrue(configMap.containsKey(ABI_1 + " suite/stub1"));
+ assertTrue(configMap.containsKey(ABI_1 + " suite/stub2"));
+ assertTrue(configMap.containsKey(ABI_2 + " suite/stub1"));
+ assertTrue(configMap.containsKey(ABI_2 + " suite/stub2"));
+ EasyMock.verify(mockBuildInfo);
+ } finally {
+ FileUtil.recursiveDelete(tempDir);
+ }
+ }
+
+ /**
+ * Test for {@link TestMappingSuiteRunner#getTestInfos(Set, String)} that when a module is
+ * specified, tests would be still found correctly.
+ */
+ @Test
+ public void testGetTestInfos() throws Exception {
+ Set<TestInfo> testInfos = new HashSet<>();
+ testInfos.add(createTestInfo("test", "path"));
+ testInfos.add(createTestInfo("test", "path2"));
+ testInfos.add(createTestInfo("test2", "path2"));
+
+ assertEquals(2, mRunner.getTestInfos(testInfos, "test").size());
+ assertEquals(1, mRunner.getTestInfos(testInfos, "test2").size());
+ }
+
+ /**
+ * Test for {@link TestMappingSuiteRunner#dedupTestInfos(Set)} that tests with the same test
+ * options would be filtered out.
+ */
+ @Test
+ public void testDedupTestInfos() throws Exception {
+ Set<TestInfo> testInfos = new HashSet<>();
+ testInfos.add(createTestInfo("test", "path"));
+ testInfos.add(createTestInfo("test", "path2"));
+ assertEquals(1, mRunner.dedupTestInfos(testInfos).size());
+
+ TestInfo anotherInfo = new TestInfo("test", "folder3", false);
+ anotherInfo.addOption(new TestOption("include-filter", "value1"));
+ testInfos.add(anotherInfo);
+ assertEquals(2, mRunner.dedupTestInfos(testInfos).size());
+ }
+
+ /**
+ * Test for {@link TestMappingSuiteRunner#getTestSources(Set)} that test sources would be found
+ * correctly.
+ */
+ @Test
+ public void testGetTestSources() throws Exception {
+ Set<TestInfo> testInfos = new HashSet<>();
+ testInfos.add(createTestInfo("test", "path"));
+ testInfos.add(createTestInfo("test", "path2"));
+ List<String> results = mRunner.getTestSources(testInfos);
+ assertEquals(2, results.size());
+ }
+
+ /**
+ * Test for {@link TestMappingSuiteRunner#parseOptions(TestInfo)} that the test options are
+ * injected correctly.
+ */
+ @Test
+ public void testParseOptions() throws Exception {
+ TestInfo info = createTestInfo("test", "path");
+ mRunner.parseOptions(info);
+ assertEquals(1, mRunner.getIncludeFilter().size());
+ assertEquals(1, mRunner.getExcludeFilter().size());
+ }
+
+ /**
+ * Test for {@link TestMappingSuiteRunner#createIndividualTests(Set, String)} that IRemoteTest
+ * object are created according to the test infos with different test options.
+ */
+ @Test
+ public void testCreateIndividualTestsWithDifferentTestInfos() throws Exception {
+ File tempDir = null;
+ try {
+ tempDir = FileUtil.createTempDir("tmp");
+ File moduleConfig = new File(tempDir, "module_name.config");
+ moduleConfig.createNewFile();
+ Set<TestInfo> testInfos = new HashSet<>();
+ testInfos.add(createTestInfo("test", "path"));
+ testInfos.add(createTestInfo("test2", "path"));
+ String configPath = moduleConfig.getAbsolutePath();
+ assertEquals(2, mRunner2.createIndividualTests(testInfos, configPath).size());
+ assertEquals(1, mRunner2.getIncludeFilter().size());
+ assertEquals(1, mRunner2.getExcludeFilter().size());
+ } finally {
+ FileUtil.recursiveDelete(tempDir);
+ }
+ }
+
+ /**
+ * Test for {@link TestMappingSuiteRunner#createIndividualTests(Set, String)} that IRemoteTest
+ * object are created according to the test infos with multiple test options.
+ */
+ @Test
+ public void testCreateIndividualTestsWithDifferentTestOptions() throws Exception {
+ File tempDir = null;
+ try {
+ tempDir = FileUtil.createTempDir("tmp");
+ File moduleConfig = new File(tempDir, "module_name.config");
+ moduleConfig.createNewFile();
+ Set<TestInfo> testInfos = new HashSet<>();
+ testInfos.add(createTestInfo("test", "path"));
+ TestInfo info = new TestInfo("test", "path", false);
+ info.addOption(new TestOption("include-filter", "include-filter"));
+ testInfos.add(info);
+ String configPath = moduleConfig.getAbsolutePath();
+ assertEquals(2, mRunner2.createIndividualTests(testInfos, configPath).size());
+ assertEquals(1, mRunner2.getIncludeFilter().size());
+ assertEquals(0, mRunner2.getExcludeFilter().size());
+ } finally {
+ FileUtil.recursiveDelete(tempDir);
+ }
+ }
+
+ /**
+ * Test for {@link TestMappingSuiteRunner#createIndividualTests(Set, String)} that IRemoteTest
+ * object are created according to the test infos with the same test options and name.
+ */
+ @Test
+ public void testCreateIndividualTestsWithSameTestInfos() throws Exception {
+ File tempDir = null;
+ try {
+ tempDir = FileUtil.createTempDir("tmp");
+ File moduleConfig = new File(tempDir, "module_name.config");
+ moduleConfig.createNewFile();
+ String configPath = moduleConfig.getAbsolutePath();
+ Set<TestInfo> testInfos = new HashSet<>();
+ testInfos.add(createTestInfo("test", "path"));
+ testInfos.add(createTestInfo("test", "path"));
+ assertEquals(1, mRunner2.createIndividualTests(testInfos, configPath).size());
+ assertEquals(1, mRunner2.getIncludeFilter().size());
+ assertEquals(1, mRunner2.getExcludeFilter().size());
+ } finally {
+ FileUtil.recursiveDelete(tempDir);
+ }
+ }
+
+ /** Helper to create specific test infos. */
+ private TestInfo createTestInfo(String name, String source) {
+ TestInfo info = new TestInfo(name, source, false);
+ info.addOption(new TestOption("include-filter", name));
+ info.addOption(new TestOption("exclude-filter", name));
+ info.addOption(new TestOption("other", name));
+ return info;
+ }
}
diff --git a/tests/src/com/android/tradefed/testtype/suite/TestSuiteStub.java b/tests/src/com/android/tradefed/testtype/suite/TestSuiteStub.java
index 58a3e03..4e7aac2 100644
--- a/tests/src/com/android/tradefed/testtype/suite/TestSuiteStub.java
+++ b/tests/src/com/android/tradefed/testtype/suite/TestSuiteStub.java
@@ -86,6 +86,8 @@
description = "The notAnnotation class name of the test name to run, can be repeated")
private Set<String> mExcludeAnnotationFilter = new HashSet<>();
+ private Set<String> mExcludeFilters = new HashSet<>();
+
/** Tests attempt. */
private void testAttempt(ITestInvocationListener listener) throws DeviceNotAvailableException {
listener.testRunStarted(mModule, 3);
@@ -224,10 +226,14 @@
public void addAllIncludeFilters(Set<String> filters) {}
@Override
- public void addExcludeFilter(String filter) {}
+ public void addExcludeFilter(String filter) {
+ mExcludeFilters.add(filter);
+ }
@Override
- public void addAllExcludeFilters(Set<String> filters) {}
+ public void addAllExcludeFilters(Set<String> filters) {
+ mExcludeFilters.addAll(filters);
+ }
@Override
public void clearIncludeFilters() {}
@@ -239,11 +245,13 @@
@Override
public Set<String> getExcludeFilters() {
- return new HashSet<>();
+ return mExcludeFilters;
}
@Override
- public void clearExcludeFilters() {}
+ public void clearExcludeFilters() {
+ mExcludeFilters.clear();
+ }
@Override
public void addIncludeAnnotation(String annotation) {
diff --git a/tests/src/com/android/tradefed/testtype/suite/retry/RetryReschedulerTest.java b/tests/src/com/android/tradefed/testtype/suite/retry/RetryReschedulerTest.java
index 380e75c..000e565 100644
--- a/tests/src/com/android/tradefed/testtype/suite/retry/RetryReschedulerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/retry/RetryReschedulerTest.java
@@ -347,6 +347,45 @@
verify(mSuite).setExcludeFilter(excludeRun1);
}
+ /** Ensure that the --module option can be used to force a single module to run. */
+ @Test
+ public void testReschedule_module_option() throws Exception {
+ OptionSetter setter = new OptionSetter(mTest);
+ setter.setOptionValue(BaseTestSuite.MODULE_OPTION, "run0");
+ populateFakeResults(2, 2, 1, 0, 0, false);
+ mMockLoader.init();
+ EasyMock.expect(mMockLoader.getCommandLine()).andReturn("previous_command");
+ EasyMock.expect(mMockFactory.createConfigurationFromArgs(EasyMock.anyObject()))
+ .andReturn(mRescheduledConfiguration);
+ EasyMock.expect(mMockLoader.loadPreviousRecord()).andReturn(mFakeRecord);
+
+ mRescheduledConfiguration.setTests(EasyMock.anyObject());
+ EasyMock.expectLastCall().times(1);
+
+ EasyMock.expect(mMockRescheduler.scheduleConfig(mRescheduledConfiguration)).andReturn(true);
+ EasyMock.replay(
+ mMockRescheduler,
+ mMockLoader,
+ mMockFactory,
+ mRescheduledConfiguration,
+ mMockCommandOptions);
+ mTest.run(null);
+ EasyMock.verify(
+ mMockRescheduler,
+ mMockLoader,
+ mMockFactory,
+ mRescheduledConfiguration,
+ mMockCommandOptions);
+
+ Set<String> excludeRun0 = new HashSet<>();
+ excludeRun0.add("run0 test.class#testPass0");
+ verify(mSuite).setExcludeFilter(excludeRun0);
+ Set<String> excludeRun1 = new HashSet<>();
+ // Only run0 was requested to run.
+ excludeRun1.add("run1");
+ verify(mSuite).setExcludeFilter(excludeRun1);
+ }
+
/**
* Test that if an exclude-filter is provided without abi, we are still able to exclude all the
* matching modules for all abis.
@@ -391,6 +430,47 @@
verify(mSuite).setExcludeFilter(excludeRun1);
}
+ /** Ensure that --module works when abi are present. */
+ @Test
+ public void testReschedule_moduleOption_abi() throws Exception {
+ OptionSetter setter = new OptionSetter(mTest);
+ // We specify to exclude "run1"
+ setter.setOptionValue(BaseTestSuite.MODULE_OPTION, "run0");
+ populateFakeResults(2, 2, 1, 0, 0, false, new Abi("armeabi-v7a", "32"));
+ mMockLoader.init();
+ EasyMock.expect(mMockLoader.getCommandLine()).andReturn("previous_command");
+ EasyMock.expect(mMockFactory.createConfigurationFromArgs(EasyMock.anyObject()))
+ .andReturn(mRescheduledConfiguration);
+ EasyMock.expect(mMockLoader.loadPreviousRecord()).andReturn(mFakeRecord);
+
+ mRescheduledConfiguration.setTests(EasyMock.anyObject());
+ EasyMock.expectLastCall().times(1);
+
+ EasyMock.expect(mMockRescheduler.scheduleConfig(mRescheduledConfiguration)).andReturn(true);
+ EasyMock.replay(
+ mMockRescheduler,
+ mMockLoader,
+ mMockFactory,
+ mRescheduledConfiguration,
+ mMockCommandOptions);
+ mTest.run(null);
+ EasyMock.verify(
+ mMockRescheduler,
+ mMockLoader,
+ mMockFactory,
+ mRescheduledConfiguration,
+ mMockCommandOptions);
+
+ Set<String> excludeRun0 = new HashSet<>();
+ // Run with the abi are excluded
+ excludeRun0.add("armeabi-v7a run0 test.class#testPass0");
+ verify(mSuite).setExcludeFilter(excludeRun0);
+ Set<String> excludeRun1 = new HashSet<>();
+ // Even if run1 had failed test cases, it was excluded so it's not running.
+ excludeRun1.add("armeabi-v7a run1");
+ verify(mSuite).setExcludeFilter(excludeRun1);
+ }
+
/** Test rescheduling a configuration when no parameterized tests previously failed. */
@Test
public void testReschedule_parameterized_nofail() throws Exception {
diff --git a/tests/src/com/android/tradefed/util/JavaCodeCoverageFlusherTest.java b/tests/src/com/android/tradefed/util/JavaCodeCoverageFlusherTest.java
new file mode 100644
index 0000000..a8cb7de
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/JavaCodeCoverageFlusherTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.contains;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import com.google.common.collect.ImmutableList;
+
+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 java.util.List;
+
+/** Unit tests for {@link JavaCodeCoverageFlusher}. */
+@RunWith(JUnit4.class)
+public final class JavaCodeCoverageFlusherTest {
+
+ private static final String PS_OUTPUT =
+ "USER PID PPID VSZ RSS WCHAN PC S NAME\n"
+ + "bluetooth 123 1366 123 456 SyS_epoll+ 0 S com.android.bluetooth\n"
+ + "u0_a80 4567 1366 456 789 SyS_epoll+ 0 S com.google.android.gms.persistent\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";
+
+ private static final String PM_LIST_PACKAGES_OUTPUT =
+ "package:com.android.bluetooth\n"
+ + "package:com.google.android.gms.persistent\n"
+ + "package:com.android.not.used\n"
+ + "package:com.android.phone\n";
+
+ @Mock ITestDevice mMockDevice;
+
+ // Object under test
+ JavaCodeCoverageFlusher mFlusher;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testResetAll_calledForAllProcesses() throws DeviceNotAvailableException {
+ mFlusher = new JavaCodeCoverageFlusher(mMockDevice, ImmutableList.of());
+
+ doReturn(PS_OUTPUT).when(mMockDevice).executeShellCommand("ps -e");
+ doReturn(PM_LIST_PACKAGES_OUTPUT)
+ .when(mMockDevice)
+ .executeShellCommand("pm list packages -a");
+ doReturn("123").when(mMockDevice).getProcessPid("com.android.bluetooth");
+ doReturn("4567").when(mMockDevice).getProcessPid("com.google.android.gms.persistent");
+ doReturn("890").when(mMockDevice).getProcessPid("com.android.phone");
+
+ mFlusher.resetCoverage();
+
+ verify(mMockDevice)
+ .executeShellCommand(
+ "am attach-agent com.android.bluetooth "
+ + "/system/lib/libdumpcoverage.so=reset");
+ verify(mMockDevice)
+ .executeShellCommand(
+ "am attach-agent com.google.android.gms.persistent "
+ + "/system/lib/libdumpcoverage.so=reset");
+ verify(mMockDevice)
+ .executeShellCommand(
+ "am attach-agent com.android.phone /system/lib/libdumpcoverage.so=reset");
+
+ verify(mMockDevice).executeShellCommand("cmd coverage reset");
+ }
+
+ @Test
+ public void testResetSpecific_calledForSpecificProcesses() throws DeviceNotAvailableException {
+ mFlusher = new JavaCodeCoverageFlusher(mMockDevice, ImmutableList.of("com.android.phone"));
+
+ doReturn(PS_OUTPUT).when(mMockDevice).executeShellCommand("ps -e");
+ doReturn(PM_LIST_PACKAGES_OUTPUT)
+ .when(mMockDevice)
+ .executeShellCommand("pm list packages -a");
+ doReturn("123").when(mMockDevice).getProcessPid("com.android.phone");
+
+ mFlusher.resetCoverage();
+
+ verify(mMockDevice)
+ .executeShellCommand(
+ "am attach-agent com.android.phone /system/lib/libdumpcoverage.so=reset");
+
+ verify(mMockDevice).executeShellCommand("cmd coverage reset");
+ }
+
+ @Test
+ public void testDumpAll_calledForAllProcesses() throws DeviceNotAvailableException {
+ mFlusher = new JavaCodeCoverageFlusher(mMockDevice, ImmutableList.of());
+
+ doReturn(PS_OUTPUT).when(mMockDevice).executeShellCommand("ps -e");
+ doReturn(PM_LIST_PACKAGES_OUTPUT)
+ .when(mMockDevice)
+ .executeShellCommand("pm list packages -a");
+ doReturn("123").when(mMockDevice).getProcessPid("com.android.bluetooth");
+ doReturn("4567").when(mMockDevice).getProcessPid("com.google.android.gms.persistent");
+ doReturn("890").when(mMockDevice).getProcessPid("com.android.phone");
+
+ List<String> coverageFiles = mFlusher.forceCoverageFlush();
+
+ assertThat(coverageFiles)
+ .containsExactly(
+ "/data/misc/trace/com.android.bluetooth-123.ec",
+ "/data/misc/trace/com.google.android.gms.persistent-4567.ec",
+ "/data/misc/trace/com.android.phone-890.ec",
+ "/data/misc/trace/system_server.ec");
+
+ verify(mMockDevice)
+ .executeShellCommand(
+ "am attach-agent com.android.bluetooth /system/lib/libdumpcoverage.so="
+ + "dump:/data/misc/trace/com.android.bluetooth-123.ec");
+ verify(mMockDevice)
+ .executeShellCommand(
+ "am attach-agent com.google.android.gms.persistent "
+ + "/system/lib/libdumpcoverage.so=dump:"
+ + "/data/misc/trace/com.google.android.gms.persistent-4567.ec");
+ verify(mMockDevice)
+ .executeShellCommand(
+ "am attach-agent com.android.phone /system/lib/libdumpcoverage.so="
+ + "dump:/data/misc/trace/com.android.phone-890.ec");
+
+ verify(mMockDevice).executeShellCommand(contains("cmd coverage dump"));
+ }
+
+ @Test
+ public void testDumpSpecific_calledForSpecificProcesses() throws DeviceNotAvailableException {
+ mFlusher =
+ new JavaCodeCoverageFlusher(
+ mMockDevice,
+ ImmutableList.of(
+ "com.android.bluetooth", "com.google.android.gms.persistent"));
+
+ doReturn(PM_LIST_PACKAGES_OUTPUT)
+ .when(mMockDevice)
+ .executeShellCommand("pm list packages -a");
+ doReturn("234").when(mMockDevice).getProcessPid("com.android.bluetooth");
+
+ // Request coverage for com.android.bluetooth (valid process) and
+ // com.google.android.gms.persistent (not a valid process, coverage is not flushed).
+ List<String> coverageFiles = mFlusher.forceCoverageFlush();
+
+ assertThat(coverageFiles)
+ .containsExactly(
+ "/data/misc/trace/com.android.bluetooth-234.ec",
+ "/data/misc/trace/system_server.ec");
+
+ verify(mMockDevice).executeShellCommand("pm list packages -a");
+ verify(mMockDevice)
+ .executeShellCommand(
+ "am attach-agent com.android.bluetooth /system/lib/libdumpcoverage.so"
+ + "=dump:/data/misc/trace/com.android.bluetooth-234.ec");
+ verify(mMockDevice).executeShellCommand(contains("cmd coverage dump"));
+ }
+}
diff --git a/tests/src/com/android/tradefed/util/NativeCodeCoverageFlusherTest.java b/tests/src/com/android/tradefed/util/NativeCodeCoverageFlusherTest.java
index 523655c..d5f627f 100644
--- a/tests/src/com/android/tradefed/util/NativeCodeCoverageFlusherTest.java
+++ b/tests/src/com/android/tradefed/util/NativeCodeCoverageFlusherTest.java
@@ -34,7 +34,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.lang.IllegalStateException;
import java.util.List;
@RunWith(JUnit4.class)
@@ -48,15 +47,14 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
- mFlusher = new NativeCodeCoverageFlusher(mMockDevice);
}
@Test
public void testClearCoverageMeasurements_rmCommandCalled() throws DeviceNotAvailableException {
doReturn(true).when(mMockDevice).isAdbRoot();
- mFlusher.clearCoverageMeasurements();
+ mFlusher = new NativeCodeCoverageFlusher(mMockDevice, ImmutableList.of());
+ mFlusher.resetCoverage();
// Verify that the rm command was executed.
verify(mMockDevice).executeShellCommand("rm -rf /data/misc/trace/*");
@@ -67,7 +65,8 @@
doReturn(false).when(mMockDevice).isAdbRoot();
try {
- mFlusher.clearCoverageMeasurements();
+ mFlusher = new NativeCodeCoverageFlusher(mMockDevice, ImmutableList.of());
+ mFlusher.resetCoverage();
fail("Should have thrown an exception");
} catch (IllegalStateException e) {
// Expected
@@ -82,7 +81,8 @@
throws DeviceNotAvailableException {
doReturn(true).when(mMockDevice).isAdbRoot();
- mFlusher.forceCoverageFlush(ImmutableList.of());
+ mFlusher = new NativeCodeCoverageFlusher(mMockDevice, ImmutableList.of());
+ mFlusher.forceCoverageFlush();
// Verify that the flush command for all processes was called.
verify(mMockDevice).executeShellCommand("kill -37 -1");
@@ -97,7 +97,8 @@
doReturn("12").when(mMockDevice).getProcessPid(processes.get(0));
doReturn("789").when(mMockDevice).getProcessPid(processes.get(1));
- mFlusher.forceCoverageFlush(processes);
+ mFlusher = new NativeCodeCoverageFlusher(mMockDevice, processes);
+ mFlusher.forceCoverageFlush();
// Verify that the flush command for the specific processes was called.
verify(mMockDevice).executeShellCommand("kill -37 12 789");
@@ -108,7 +109,8 @@
doReturn(false).when(mMockDevice).isAdbRoot();
try {
- mFlusher.forceCoverageFlush(ImmutableList.of("mediaserver"));
+ mFlusher = new NativeCodeCoverageFlusher(mMockDevice, ImmutableList.of("mediaserver"));
+ mFlusher.forceCoverageFlush();
fail("Should have thrown an exception");
} catch (IllegalStateException e) {
// Expected
diff --git a/tests/src/com/android/tradefed/util/TarUtilTest.java b/tests/src/com/android/tradefed/util/TarUtilTest.java
index cc9c267..4870b4d 100644
--- a/tests/src/com/android/tradefed/util/TarUtilTest.java
+++ b/tests/src/com/android/tradefed/util/TarUtilTest.java
@@ -88,6 +88,24 @@
}
/**
+ * Test that {TarUtil#extractTarGzipToTemp(File, String)} can extract properly a tar.gz file.
+ */
+ @Test
+ public void testExtractTarGzipToTemp() throws Exception {
+ InputStream logTarGz = getClass().getResourceAsStream(EMMA_METADATA_RESOURCE_PATH);
+ File tarGzFile = FileUtil.createTempFile("extract_tar_gz_test", ".tar.gz");
+ File tempDir = null;
+ try {
+ FileUtil.writeToFile(logTarGz, tarGzFile);
+ tempDir = TarUtil.extractTarGzipToTemp(tarGzFile, "extract_tar_gz_test");
+ Assert.assertEquals(2, tempDir.list().length);
+ } finally {
+ FileUtil.recursiveDelete(tempDir);
+ FileUtil.deleteFile(tarGzFile);
+ }
+ }
+
+ /**
* Test that {TarUtil#extractAndLog(ITestLogger, File, String)} can untar properly a tar file
* and export its content.
*/
diff --git a/tests/src/com/android/tradefed/util/testmapping/TestMappingTest.java b/tests/src/com/android/tradefed/util/testmapping/TestMappingTest.java
index 56cfa8c..c1537ca 100644
--- a/tests/src/com/android/tradefed/util/testmapping/TestMappingTest.java
+++ b/tests/src/com/android/tradefed/util/testmapping/TestMappingTest.java
@@ -32,8 +32,11 @@
import java.io.File;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -167,12 +170,10 @@
assertEquals(0, tests.size());
tests = TestMapping.getTests(mockBuildInfo, "presubmit", true, null);
- assertEquals(1, tests.size());
+ assertEquals(2, tests.size());
Set<String> names = new HashSet<String>();
for (TestInfo test : tests) {
names.add(test.getName());
- // Make sure the tests for `test1` are merged and no option is kept.
- assertTrue(test.getOptions().isEmpty());
if (test.getName().equals("test1")) {
assertTrue(test.getHostOnly());
} else {
@@ -228,6 +229,93 @@
}
/**
+ * Test for {@link TestMapping#getAllTestMappingPaths(Path)} to get TEST_MAPPING files from
+ * child directory.
+ */
+ @Test
+ public void testGetAllTestMappingPaths_FromChildDirectory() throws Exception {
+ File tempDir = null;
+ try {
+ tempDir = FileUtil.createTempDir("test_mapping");
+ Path testMappingsRootPath = Paths.get(tempDir.getAbsolutePath());
+ File srcDir = FileUtil.createTempDir("src", tempDir);
+ String srcFile = File.separator + TEST_DATA_DIR + File.separator + "test_mapping_1";
+ InputStream resourceStream = this.getClass().getResourceAsStream(srcFile);
+ FileUtil.saveResourceFile(resourceStream, srcDir, TEST_MAPPING);
+ File subDir = FileUtil.createTempDir("sub_dir", srcDir);
+ srcFile = File.separator + TEST_DATA_DIR + File.separator + "test_mapping_2";
+ resourceStream = this.getClass().getResourceAsStream(srcFile);
+ FileUtil.saveResourceFile(resourceStream, subDir, TEST_MAPPING);
+
+ List<String> testMappingRelativePaths = new ArrayList<>();
+ Path relPath = testMappingsRootPath.relativize(Paths.get(subDir.getAbsolutePath()));
+ testMappingRelativePaths.add(relPath.toString());
+ TestMapping.setTestMappingPaths(testMappingRelativePaths);
+ Set<Path> paths = TestMapping.getAllTestMappingPaths(testMappingsRootPath);
+ assertEquals(2, paths.size());
+ } finally {
+ TestMapping.setTestMappingPaths(new ArrayList<>());
+ FileUtil.recursiveDelete(tempDir);
+ }
+ }
+
+ /**
+ * Test for {@link TestMapping#getAllTestMappingPaths(Path)} to get TEST_MAPPING files from
+ * parent directory.
+ */
+ @Test
+ public void testGetAllTestMappingPaths_FromParentDirectory() throws Exception {
+ File tempDir = null;
+ try {
+ tempDir = FileUtil.createTempDir("test_mapping");
+ Path testMappingsRootPath = Paths.get(tempDir.getAbsolutePath());
+ File srcDir = FileUtil.createTempDir("src", tempDir);
+ String srcFile = File.separator + TEST_DATA_DIR + File.separator + "test_mapping_1";
+ InputStream resourceStream = this.getClass().getResourceAsStream(srcFile);
+ FileUtil.saveResourceFile(resourceStream, srcDir, TEST_MAPPING);
+ File subDir = FileUtil.createTempDir("sub_dir", srcDir);
+ srcFile = File.separator + TEST_DATA_DIR + File.separator + "test_mapping_2";
+ resourceStream = this.getClass().getResourceAsStream(srcFile);
+ FileUtil.saveResourceFile(resourceStream, subDir, TEST_MAPPING);
+
+ List<String> testMappingRelativePaths = new ArrayList<>();
+ Path relPath = testMappingsRootPath.relativize(Paths.get(srcDir.getAbsolutePath()));
+ testMappingRelativePaths.add(relPath.toString());
+ TestMapping.setTestMappingPaths(testMappingRelativePaths);
+ Set<Path> paths = TestMapping.getAllTestMappingPaths(testMappingsRootPath);
+ assertEquals(1, paths.size());
+ } finally {
+ TestMapping.setTestMappingPaths(new ArrayList<>());
+ FileUtil.recursiveDelete(tempDir);
+ }
+ }
+
+ /**
+ * Test for {@link TestMapping#getAllTestMappingPaths(Path)} to fail when no TEST_MAPPING files
+ * found.
+ */
+ @Test(expected = RuntimeException.class)
+ public void testGetAllTestMappingPaths_NoFilesFound() throws Exception {
+ File tempDir = null;
+ try {
+ tempDir = FileUtil.createTempDir("test_mapping");
+ Path testMappingsRootPath = Paths.get(tempDir.getAbsolutePath());
+ File srcDir = FileUtil.createTempDir("src", tempDir);
+
+ List<String> testMappingRelativePaths = new ArrayList<>();
+ Path relPath = testMappingsRootPath.relativize(Paths.get(srcDir.getAbsolutePath()));
+ testMappingRelativePaths.add(relPath.toString());
+ TestMapping.setTestMappingPaths(testMappingRelativePaths);
+ // No TEST_MAPPING files should be found according to the srcDir, getAllTestMappingPaths
+ // method shall raise RuntimeException.
+ TestMapping.getAllTestMappingPaths(testMappingsRootPath);
+ } finally {
+ TestMapping.setTestMappingPaths(new ArrayList<>());
+ FileUtil.recursiveDelete(tempDir);
+ }
+ }
+
+ /**
* Test for {@link TestInfo#merge()} for merging two TestInfo objects to fail when module names
* are different.
*/
@@ -504,4 +592,35 @@
FileUtil.recursiveDelete(tempDir);
}
}
+
+ /** Test for {@link TestMapping#removeComments()} for removing comments in TEST_MAPPING file. */
+ @Test
+ public void testRemoveComments() throws Exception {
+ String jsonString = getJsonStringByName("test_mapping_with_comments1");
+ String goldenString = getJsonStringByName("test_mapping_golden1");
+ assertEquals(TestMapping.removeComments(jsonString), goldenString);
+ }
+
+ /** Test for {@link TestMapping#removeComments()} for removing comments in TEST_MAPPING file. */
+ @Test
+ public void testRemoveComments2() throws Exception {
+ String jsonString = getJsonStringByName("test_mapping_with_comments2");
+ String goldenString = getJsonStringByName("test_mapping_golden2");
+ assertEquals(TestMapping.removeComments(jsonString), goldenString);
+ }
+
+ private String getJsonStringByName(String fileName) throws Exception {
+ File tempDir = null;
+ try {
+ tempDir = FileUtil.createTempDir("test_mapping");
+ File srcDir = FileUtil.createTempDir("src", tempDir);
+ String srcFile = File.separator + TEST_DATA_DIR + File.separator + fileName;
+ InputStream resourceStream = this.getClass().getResourceAsStream(srcFile);
+ FileUtil.saveResourceFile(resourceStream, srcDir, TEST_MAPPING);
+ Path file = Paths.get(srcDir.getAbsolutePath(), TEST_MAPPING);
+ return String.join("\n", Files.readAllLines(file, StandardCharsets.UTF_8));
+ } finally {
+ FileUtil.recursiveDelete(tempDir);
+ }
+ }
}