diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-08 16:04:56 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-08 16:04:56 +0000 |
commit | 87e2860d8eb7447a0a5e05e92f5a2cc8b33c1202 (patch) | |
tree | b9975efd1ab97f81ec518ea6324ea86da608ded9 | |
parent | 1c0ac36028cc7a26eeaa6256c5e37711d95e0fbc (diff) | |
parent | 5c86fc9b9b042528f9af6ea7c53c22f892ed3b2f (diff) | |
download | csuite-aml_tz2_305400100.tar.gz |
Snap for 8426163 from 5c86fc9b9b042528f9af6ea7c53c22f892ed3b2f to mainline-tzdata2-releaseandroid-mainline-12.0.0_r112aml_tz2_305400500aml_tz2_305400300aml_tz2_305400100aml_tz2_304500300aml_tz2_303900110aml_tz2_303900102aml_tz2_303800002aml_tz2_303800001aml_tz2_303200001android12-mainline-tzdata2-releaseaml_tz2_305400100
Change-Id: I2f9e39e9ddf0308a08cf96148bdb35de086733ef
74 files changed, 1933 insertions, 6047 deletions
diff --git a/Android.bp b/Android.bp deleted file mode 100644 index 5295e32..0000000 --- a/Android.bp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -python_defaults { - name: "csuite_python_defaults", - version: { - py2: { - enabled: false, - }, - py3: { - enabled: true, - }, - }, -} @@ -1,5 +0,0 @@ -adirao@google.com -fdeng@google.com -hzalek@google.com -yuexima@google.com -zhuoyao@google.com diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 91c7914..948fa79 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,6 +1,5 @@ [Builtin Hooks] bpfmt = true -gofmt = true google_java_format = true pylint = true xmllint = true diff --git a/harness/Android.bp b/harness/Android.bp index 5111c1b..2dec437 100644 --- a/harness/Android.bp +++ b/harness/Android.bp @@ -12,10 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - java_library_host { name: "csuite-harness", srcs: [ @@ -27,9 +23,6 @@ java_library_host { libs: [ "tradefed", ], - static_libs: [ - "compatibility-tradefed", - ] } java_test_host { @@ -37,17 +30,14 @@ java_test_host { srcs: [ "src/test/java/**/*.java", ], - static_libs: [ - "tradefed", + libs: [ "csuite-harness", - "compatibility-tradefed", - "guava-testlib", - "jimfs", + "tradefed", + ], + static_libs: [ "mockito-host", "objenesis", "testng", ], - test_options: { - unit_test: true, - }, + test_suites: ["general-tests"], } diff --git a/harness/AndroidTest.xml b/harness/AndroidTest.xml new file mode 100644 index 0000000..f3fb637 --- /dev/null +++ b/harness/AndroidTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Executes the C-Suite harness unit tests"> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="class" value="com.android.compatibility.CSuiteUnitTests" /> + </test> +</configuration> diff --git a/harness/TEST_MAPPING b/harness/TEST_MAPPING new file mode 100644 index 0000000..50ed1cd --- /dev/null +++ b/harness/TEST_MAPPING @@ -0,0 +1,8 @@ +{ + "postsubmit": [ + { + "name": "csuite-harness-tests", + "host": true + } + ] +} diff --git a/harness/src/main/java/com/android/compatibility/AppCompatibilityTest.java b/harness/src/main/java/com/android/compatibility/AppCompatibilityTest.java new file mode 100644 index 0000000..74c4907 --- /dev/null +++ b/harness/src/main/java/com/android/compatibility/AppCompatibilityTest.java @@ -0,0 +1,671 @@ +/* + * Copyright (C) 2012 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; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +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.OptionCopier; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.DeviceUnresponsiveException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.device.LogcatReceiver; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; +import com.android.tradefed.result.ByteArrayInputStreamSource; +import com.android.tradefed.result.CompatibilityTestResult; +import com.android.tradefed.result.ITestInvocationListener; +import com.android.tradefed.result.InputStreamSource; +import com.android.tradefed.result.LogDataType; +import com.android.tradefed.result.TestDescription; +import com.android.tradefed.testtype.IDeviceTest; +import com.android.tradefed.testtype.IRemoteTest; +import com.android.tradefed.testtype.IShardableTest; +import com.android.tradefed.testtype.ITestFilterReceiver; +import com.android.tradefed.testtype.InstrumentationTest; +import com.android.tradefed.util.AaptParser; +import com.android.tradefed.util.FileUtil; +import com.android.tradefed.util.IRunUtil; +import com.android.tradefed.util.PublicApkUtil; +import com.android.tradefed.util.PublicApkUtil.ApkInfo; +import com.android.tradefed.util.RunUtil; +import com.android.tradefed.util.StreamUtil; + +import com.google.common.base.Strings; + +import org.json.JSONException; +import org.junit.Assert; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Test that determines application compatibility. The test iterates through the apks in a given + * directory. The test installs, launches, and uninstalls each apk. + */ +public abstract class AppCompatibilityTest + implements IDeviceTest, + IRemoteTest, + IShardableTest, + IConfigurationReceiver, + ITestFilterReceiver { + + @Option( + name = "product", + description = "The product, corresponding to the borgcron job product arg.") + private String mProduct; + + @Option( + name = "base-dir", + description = "The directory of the results excluding the date.", + importance = Option.Importance.ALWAYS) + // TODO(b/36786754): Add `mandatory = true` when cmdfiles are moved over + private File mBaseDir; + + @Option( + name = "date", + description = + "The date to run, in the form YYYYMMDD. If not set, then the latest " + + "results will be used.") + private String mDate; + + @Option(name = "test-label", description = "Unique test identifier label.") + private String mTestLabel = "AppCompatibility"; + + @Option( + name = "reboot-after-apks", + description = "Reboot the device after a centain number of apks. 0 means no reboot.") + private int mRebootNumber = 100; + + @Option( + name = "fallback-to-apk-scan", + description = + "Fallback to scanning for apks in base directory if ranking information " + + "is missing.") + private boolean mFallbackToApkScan = false; + + @Option( + name = "retry-count", + description = "Number of times to retry a failed test case. 0 means no retry.") + private int mRetryCount = 5; + + @Option(name = "include-filter", description = "The include filter of the test names to run.") + protected Set<String> mIncludeFilters = new HashSet<>(); + + @Option(name = "exclude-filter", description = "The exclude filter of the test names to run.") + protected Set<String> mExcludeFilters = new HashSet<>(); + + private static final long DOWNLOAD_TIMEOUT_MS = 60 * 1000; + private static final int DOWNLOAD_RETRIES = 3; + private static final long JOIN_TIMEOUT_MS = 5 * 60 * 1000; + private static final int LOGCAT_SIZE_BYTES = 20 * 1024 * 1024; + + private ITestDevice mDevice; + private LogcatReceiver mLogcat; + private IConfiguration mConfiguration; + + // The number of tests run so far + private int mTestCount = 0; + + // indicates the current sharding setup + private int mShardCount = 1; + private int mShardIndex = 0; + + protected final String mLauncherPackage; + protected final String mRunnerClass; + protected final String mPackageBeingTestedKey; + + protected AppCompatibilityTest( + String launcherPackage, String runnerClass, String packageBeingTestedKey) { + this.mLauncherPackage = launcherPackage; + this.mRunnerClass = runnerClass; + this.mPackageBeingTestedKey = packageBeingTestedKey; + } + + /** + * Creates and sets up an instrumentation test with information about the test runner as well as + * the package being tested (provided as a parameter). + */ + protected abstract InstrumentationTest createInstrumentationTest(String packageBeingTested); + + /** Sets up some default aspects of the instrumentation test. */ + protected final InstrumentationTest createDefaultInstrumentationTest( + String packageBeingTested) { + InstrumentationTest instrTest = new InstrumentationTest(); + instrTest.setPackageName(mLauncherPackage); + instrTest.setConfiguration(mConfiguration); + instrTest.addInstrumentationArg(mPackageBeingTestedKey, packageBeingTested); + instrTest.setRunnerName(mRunnerClass); + return instrTest; + } + + /* + * {@inheritDoc} + */ + @Override + public final void run(ITestInvocationListener listener) throws DeviceNotAvailableException { + CLog.d("Start of launch test run method. base-dir: %s", mBaseDir); + Assert.assertNotNull("Base dir cannot be null", mBaseDir); + Assert.assertTrue("Base dir should be a directory", mBaseDir.isDirectory()); + + if (mProduct == null) { + mProduct = mDevice.getProductType(); + CLog.i("\"--product\" not specified, using property from device instead: %s", mProduct); + } + Assert.assertTrue( + String.format( + "Shard index out of range: expected [0, %d), got %d", + mShardCount, mShardIndex), + mShardIndex >= 0 && mShardIndex < mShardCount); + + File apkDir = null; + try { + apkDir = PublicApkUtil.constructApkDir(mBaseDir.getPath(), mDate); + } catch (IOException e) { + CLog.e(e); + throw new RuntimeException(e); + } + CLog.d("apkDir: %s.", apkDir); + Assert.assertNotNull("Could not find the output dir", apkDir); + List<ApkInfo> apkList = null; + try { + apkList = shardApkList(PublicApkUtil.getApkList(mProduct, apkDir, mFallbackToApkScan)); + } catch (IOException e) { + CLog.e(e); + throw new RuntimeException(e); + } + CLog.d("Completed sharding apkList. Number of items: %s", apkList.size()); + Assert.assertNotNull("Could not download apk list", apkList); + + apkList = filterApk(apkList); + CLog.d("Completed filtering apkList. Number of items: %s", apkList.size()); + + long start = System.currentTimeMillis(); + listener.testRunStarted(mTestLabel, apkList.size()); + mLogcat = new LogcatReceiver(getDevice(), LOGCAT_SIZE_BYTES, 0); + mLogcat.start(); + + try { + downloadAndTestApks(listener, apkDir, apkList); + } catch (InterruptedException e) { + CLog.e(e); + throw new RuntimeException(e); + } finally { + mLogcat.stop(); + listener.testRunEnded( + System.currentTimeMillis() - start, new HashMap<String, Metric>()); + } + } + + /** + * Downloads and tests all the APKs in the apk list. + * + * @param listener The {@link ITestInvocationListener}. + * @param kharonDir The {@link File} of the CNS dir containing the APKs. + * @param apkList The sharded list of {@link ApkInfo} objects. + * @throws DeviceNotAvailableException + * @throws InterruptedException if a download thread was interrupted. + */ + private void downloadAndTestApks( + ITestInvocationListener listener, File kharonDir, List<ApkInfo> apkList) + throws DeviceNotAvailableException, InterruptedException { + CLog.d("Started downloading and testing apks."); + ApkInfo testingApk = null; + File testingFile = null; + for (ApkInfo downloadingApk : apkList) { + ApkDownloadRunnable downloader = new ApkDownloadRunnable(kharonDir, downloadingApk); + Thread downloadThread = new Thread(downloader); + downloadThread.start(); + + testApk(listener, testingApk, testingFile); + + try { + downloadThread.join(JOIN_TIMEOUT_MS); + } catch (InterruptedException e) { + FileUtil.deleteFile(downloader.getDownloadedFile()); + throw e; + } + testingApk = downloadingApk; + testingFile = downloader.getDownloadedFile(); + } + // One more time since the first time through the loop we don't test + testApk(listener, testingApk, testingFile); + CLog.d("Completed downloading and testing apks."); + } + + /** + * Attempts to install and launch an APK and reports the results. + * + * @param listener The {@link ITestInvocationListener}. + * @param apkInfo The {@link ApkInfo} to run the test against. + * @param apkFile The downloaded {@link File}. + * @throws DeviceNotAvailableException + */ + private void testApk(ITestInvocationListener listener, ApkInfo apkInfo, File apkFile) + throws DeviceNotAvailableException { + if (apkInfo == null || apkFile == null) { + CLog.d("apkInfo or apkFile is null."); + FileUtil.deleteFile(apkFile); + return; + } + CLog.d( + "Started testing package: %s, apk file: %s.", + apkInfo.packageName, apkFile.getAbsolutePath()); + + mTestCount++; + if (mRebootNumber != 0 && mTestCount % mRebootNumber == 0) { + mDevice.reboot(); + } + mLogcat.clear(); + + TestDescription testId = createTestDescription(apkInfo.packageName); + listener.testStarted(testId, System.currentTimeMillis()); + + CompatibilityTestResult result = new CompatibilityTestResult(); + result.rank = apkInfo.rank; + // Default to package name since name is a required field. This will be replaced by + // AaptParser in installApk() + result.name = apkInfo.packageName; + result.packageName = apkInfo.packageName; + result.versionString = apkInfo.versionString; + result.versionCode = apkInfo.versionCode; + + try { + // Install the app, and also skip aapt check if we've fell back to apk scan + installApk(result, apkFile, mFallbackToApkScan); + boolean installationSuccess = result.status == null; + + for (int i = 0; i <= mRetryCount; i++) { + if (installationSuccess) { + // Clear test result between retries + result.status = null; + result.message = null; + launchApk(result); + mDevice.executeShellCommand( + String.format("am force-stop %s", apkInfo.packageName)); + } + if (result.status == null) { + result.status = CompatibilityTestResult.STATUS_SUCCESS; + break; + } + } + + if (installationSuccess) { + mDevice.uninstallPackage(result.packageName); + } + } finally { + reportResult(listener, testId, result); + try { + postLogcat(result, listener); + } catch (JSONException e) { + CLog.w("Posting failed: %s.", e.getMessage()); + } + listener.testEnded( + testId, System.currentTimeMillis(), Collections.<String, String>emptyMap()); + FileUtil.deleteFile(apkFile); + CLog.d("Completed testing package: %s.", apkInfo.packageName); + } + } + + /** + * Checks that the file is correct and attempts to install it. + * + * <p>Will set the result status to error if the APK could not be installed or if it contains + * conflicting information. + * + * @param result the {@link CompatibilityTestResult} containing the APK info. + * @param apkFile the APK file to install. + * @throws DeviceNotAvailableException + */ + private void installApk(CompatibilityTestResult result, File apkFile, boolean skipAaptCheck) + throws DeviceNotAvailableException { + if (!skipAaptCheck) { + CLog.d("Parsing apk file: %s.", apkFile.getAbsolutePath()); + AaptParser parser = AaptParser.parse(apkFile); + if (parser == null) { + CLog.d( + "Failed to parse apk file: %s, package: %s, error: %s.", + apkFile.getAbsolutePath(), result.packageName, result.message); + result.status = CompatibilityTestResult.STATUS_ERROR; + result.message = "aapt fail"; + return; + } + + result.name = parser.getLabel(); + + if (!equalsOrNull(result.packageName, parser.getPackageName()) + || !equalsOrNull(result.versionString, parser.getVersionName()) + || !equalsOrNull(result.versionCode, parser.getVersionCode())) { + CLog.d( + "Package info mismatch: want %s v%s (%s), got %s v%s (%s)", + result.packageName, + result.versionCode, + result.versionString, + parser.getPackageName(), + parser.getVersionCode(), + parser.getVersionName()); + result.status = CompatibilityTestResult.STATUS_ERROR; + result.message = "package info mismatch"; + return; + } + CLog.d("Completed parsing apk file: %s.", apkFile.getAbsolutePath()); + } + + try { + String error = mDevice.installPackage(apkFile, true); + if (error != null) { + result.status = CompatibilityTestResult.STATUS_ERROR; + result.message = error; + CLog.d( + "Failed to install apk file: %s, package: %s, error: %s.", + apkFile.getAbsolutePath(), result.packageName, result.message); + return; + } + } catch (DeviceUnresponsiveException e) { + result.status = CompatibilityTestResult.STATUS_ERROR; + result.message = "install timeout"; + CLog.d( + "Installing apk file %s timed out, package: %s, error: %s.", + apkFile.getAbsolutePath(), result.packageName, result.message); + return; + } + CLog.d("Completed installing apk file %s.", apkFile.getAbsolutePath()); + } + + /** + * Method which attempts to launch an APK. + * + * <p>Will set the result status to failure if the APK could not be launched. + * + * @param result the {@link CompatibilityTestResult} containing the APK info. + * @throws DeviceNotAvailableException + */ + private void launchApk(CompatibilityTestResult result) throws DeviceNotAvailableException { + CLog.d("Lauching package: %s.", result.packageName); + InstrumentationTest instrTest = createInstrumentationTest(result.packageName); + instrTest.setDevice(mDevice); + + FailureCollectingListener failureListener = new FailureCollectingListener(); + instrTest.run(failureListener); + + if (failureListener.getStackTrace() != null) { + CLog.w("Failed to launch package: %s.", result.packageName); + result.status = CompatibilityTestResult.STATUS_FAILURE; + result.message = failureListener.getStackTrace(); + } + + CLog.d("Completed launching package: %s", result.packageName); + } + + /** Helper method which reports a test failed if the status is either a failure or an error. */ + private void reportResult( + ITestInvocationListener listener, TestDescription id, CompatibilityTestResult result) { + String message = result.message != null ? result.message : "unknown"; + if (CompatibilityTestResult.STATUS_ERROR.equals(result.status)) { + listener.testFailed(id, "ERROR:" + message); + } else if (CompatibilityTestResult.STATUS_FAILURE.equals(result.status)) { + listener.testFailed(id, "FAILURE:" + message); + } + } + + /** Helper method which posts the logcat. */ + private void postLogcat(CompatibilityTestResult result, ITestInvocationListener listener) + throws JSONException { + InputStreamSource stream = null; + String header = + String.format( + "%s%s%s\n", + CompatibilityTestResult.SEPARATOR, + result.toJsonString(), + CompatibilityTestResult.SEPARATOR); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (InputStreamSource logcatData = mLogcat.getLogcatData()) { + try { + baos.write(header.getBytes()); + StreamUtil.copyStreams(logcatData.createInputStream(), baos); + stream = new ByteArrayInputStreamSource(baos.toByteArray()); + baos.flush(); + baos.close(); + } catch (IOException e) { + CLog.e("error inserting compatibility test result into logcat"); + CLog.e(e); + // fallback to logcat data + stream = logcatData; + } + listener.testLog("logcat_" + result.packageName, LogDataType.LOGCAT, stream); + } finally { + StreamUtil.cancel(stream); + } + } + + /** Helper method which takes a list of {@link ApkInfo} objects and returns the sharded list. */ + private List<ApkInfo> shardApkList(List<ApkInfo> apkList) { + List<ApkInfo> shardedList = new ArrayList<>(apkList.size() / mShardCount + 1); + for (int i = mShardIndex; i < apkList.size(); i += mShardCount) { + shardedList.add(apkList.get(i)); + } + return shardedList; + } + + /** + * Helper method which takes a list of {@link ApkInfo} objects and returns the filtered list. + */ + protected List<ApkInfo> filterApk(List<ApkInfo> apkList) { + List<ApkInfo> filteredList = new ArrayList<>(); + + for (ApkInfo apk : apkList) { + if (filterTest(apk.packageName)) { + filteredList.add(apk); + } + } + + return filteredList; + } + + /** + * Return true if a test matches one or more of the include filters AND does not match any of + * the exclude filters. If no include filters are given all tests should return true as long as + * they do not match any of the exclude filters. + */ + protected boolean filterTest(String testName) { + if (mExcludeFilters.contains(testName)) { + return false; + } + if (mIncludeFilters.size() == 0 || mIncludeFilters.contains(testName)) { + return true; + } + return false; + } + + /** Returns true if either object is null or if both objects are equal. */ + private static boolean equalsOrNull(Object a, Object b) { + return a == null || b == null || a.equals(b); + } + + /** Helper {@link Runnable} which downloads a file, and can be used in another thread. */ + private class ApkDownloadRunnable implements Runnable { + private final File mKharonDir; + private final ApkInfo mApkInfo; + + private File mDownloadedFile = null; + + ApkDownloadRunnable(File kharonDir, ApkInfo apkInfo) { + mKharonDir = kharonDir; + mApkInfo = apkInfo; + } + + @Override + public void run() { + // No-op if mApkInfo is null + if (mApkInfo == null) { + CLog.d("ApkInfo is null."); + return; + } + + File sourceFile = new File(mKharonDir, mApkInfo.fileName); + try { + mDownloadedFile = + PublicApkUtil.downloadFile( + sourceFile, DOWNLOAD_TIMEOUT_MS, DOWNLOAD_RETRIES); + } catch (IOException e) { + // Log and ignore + CLog.e("Could not download apk from %s.", sourceFile); + CLog.e(e); + } + CLog.d("Completed downloading apk file: %s.", mDownloadedFile.getAbsolutePath()); + } + + public File getDownloadedFile() { + return mDownloadedFile; + } + } + + @Override + public void setConfiguration(IConfiguration configuration) { + mConfiguration = configuration; + } + + /* + * {@inheritDoc} + */ + @Override + public void setDevice(ITestDevice device) { + mDevice = device; + } + + /* + * {@inheritDoc} + */ + @Override + public ITestDevice getDevice() { + return mDevice; + } + + /** Return a {@link IRunUtil} instance to execute commands with. */ + IRunUtil getRunUtil() { + return RunUtil.getDefault(); + } + + private IRemoteTest getTestShard(int shardCount, int shardIndex) { + AppCompatibilityTest shard; + try { + shard = getClass().newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException( + "The class " + + getClass().getName() + + " has no public constructor with no arguments, but all subclasses of " + + AppCompatibilityTest.class.getName() + + " should", + e); + } + try { + OptionCopier.copyOptions(this, shard); + } catch (ConfigurationException e) { + CLog.e("Failed to copy test options: %s.", e.getMessage()); + } + shard.mShardIndex = shardIndex; + shard.mShardCount = shardCount; + return shard; + } + + /** {@inheritDoc} */ + @Override + public Collection<IRemoteTest> split(int shardCountHint) { + if (shardCountHint <= 1) { + // cannot shard or already sharded + return null; + } + Collection<IRemoteTest> shards = new ArrayList<>(shardCountHint); + for (int index = 0; index < shardCountHint; index++) { + shards.add(getTestShard(shardCountHint, index)); + } + return shards; + } + + /** + * Get a test description for use in logging. For compatibility with logs, this should be + * TestDescription(launcher package, package being run). + */ + private TestDescription createTestDescription(String packageBeingTested) { + return new TestDescription(mLauncherPackage, packageBeingTested); + } + + /** {@inheritDoc} */ + @Override + public void addIncludeFilter(String filter) { + checkArgument(!Strings.isNullOrEmpty(filter), "Include filter cannot be null or empty."); + mIncludeFilters.add(filter); + } + + /** {@inheritDoc} */ + @Override + public void addAllIncludeFilters(Set<String> filters) { + checkNotNull(filters, "Include filters cannot be null."); + mIncludeFilters.addAll(filters); + } + + /** {@inheritDoc} */ + @Override + public void clearIncludeFilters() { + mIncludeFilters.clear(); + } + + /** {@inheritDoc} */ + @Override + public Set<String> getIncludeFilters() { + return Collections.unmodifiableSet(mIncludeFilters); + } + + /** {@inheritDoc} */ + @Override + public void addExcludeFilter(String filter) { + checkArgument(!Strings.isNullOrEmpty(filter), "Exclude filter cannot be null or empty."); + mExcludeFilters.add(filter); + } + + /** {@inheritDoc} */ + @Override + public void addAllExcludeFilters(Set<String> filters) { + checkNotNull(filters, "Exclude filters cannot be null."); + mExcludeFilters.addAll(filters); + } + + /** {@inheritDoc} */ + @Override + public void clearExcludeFilters() { + mExcludeFilters.clear(); + } + + /** {@inheritDoc} */ + @Override + public Set<String> getExcludeFilters() { + return Collections.unmodifiableSet(mExcludeFilters); + } +} diff --git a/harness/src/main/java/com/android/compatibility/AppCrawlerCompatibilityTest.java b/harness/src/main/java/com/android/compatibility/AppCrawlerCompatibilityTest.java new file mode 100644 index 0000000..5c0fe11 --- /dev/null +++ b/harness/src/main/java/com/android/compatibility/AppCrawlerCompatibilityTest.java @@ -0,0 +1,82 @@ +/* + * 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.compatibility; + +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.result.TestDescription; +import com.android.tradefed.testtype.InstrumentationTest; + +import java.util.ArrayList; +import java.util.Arrays; + +@OptionClass(alias = "app-compatibility-crawler") +public final class AppCrawlerCompatibilityTest extends AppCompatibilityTest { + private static final String WALKMAN_RUN_MS_LABEL = "maxDuration"; + private static final String WALKMAN_STEPS_LABEL = "maxSteps"; + + @Option( + name = "walkman-run-ms", + description = "Time to run walkman in msecs (only used if test-strategy=walkman).") + private int mWalkmanRunMs = 60 * 1000; + + @Option( + name = "walkman-steps", + description = + "Max number of steps to run walkman (only used if test-strategy=walkman)." + + " -1 for no limit") + private int mWalkmanSteps = -1; + + public AppCrawlerCompatibilityTest() { + super( + "com.google.android.apps.common.walkman.apps", + "com.google.android.apps.common.testing.testrunner" + + ".Google3InstrumentationTestRunner", + /* + * We are using /google/data/ro/teams/walkman/walkman.apk which has parameter + * "packages" unlike the up-to-date version in source which uses "package" + * see: com.google.android.apps.common.walkman.apps.EngineFactory::getCrawlEngine. + * This currently works with the up-to-date version in source, as well. + * + * Neither of these should be confused with "package_to_launch", which is used by + * AppCompatibilityRunner + */ + "packages"); + } + + @Override + public InstrumentationTest createInstrumentationTest(String packageBeingTested) { + InstrumentationTest instrTest = createDefaultInstrumentationTest(packageBeingTested); + + instrTest.addInstrumentationArg(WALKMAN_RUN_MS_LABEL, Integer.toString(mWalkmanRunMs)); + instrTest.addInstrumentationArg(WALKMAN_STEPS_LABEL, Integer.toString(mWalkmanSteps)); + + String launcherClass = mLauncherPackage + ".WalkmanInstrumentationEntry"; + instrTest.setClassName(launcherClass); + /* + * InstrumentationTest can't deduce the exact test to run, so we specify it + * manually. Note that the TestDescription we use here is a different one from + * the one returned by {@link TestStrategy#createTestDescription}. + * + * This list is required to be mutable, so we wrap in ArrayList. + */ + instrTest.setTestsToRun( + new ArrayList<>(Arrays.asList(new TestDescription(launcherClass, "testEntry")))); + + return instrTest; + } +} diff --git a/harness/src/main/java/com/android/compatibility/AppLaunchCompatibilityTest.java b/harness/src/main/java/com/android/compatibility/AppLaunchCompatibilityTest.java new file mode 100644 index 0000000..dd1f648 --- /dev/null +++ b/harness/src/main/java/com/android/compatibility/AppLaunchCompatibilityTest.java @@ -0,0 +1,55 @@ +/* + * 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.compatibility; + +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.testtype.InstrumentationTest; + +/** Uses AppCompatibilityRunner to check if the app starts correctly. */ +@OptionClass(alias = "app-compatibility") +public final class AppLaunchCompatibilityTest extends AppCompatibilityTest { + private static final String APP_LAUNCH_TIMEOUT_LABEL = "app_launch_timeout_ms"; + private static final String WORKSPACE_LAUNCH_TIMEOUT_LABEL = "workspace_launch_timeout_ms"; + + @Option( + name = "app-launch-timeout-ms", + description = "Time to wait for app to launch in msecs.") + private int mAppLaunchTimeoutMs = 15000; + + @Option( + name = "workspace-launch-timeout-ms", + description = "Time to wait when launched back into the workspace in msecs.") + private int mWorkspaceLaunchTimeoutMs = 2000; + + public AppLaunchCompatibilityTest() { + super( + "com.android.compatibilitytest", + "com.android.compatibilitytest.AppCompatibilityRunner", + "package_to_launch"); + } + + @Override + public InstrumentationTest createInstrumentationTest(String packageBeingTested) { + InstrumentationTest instrTest = createDefaultInstrumentationTest(packageBeingTested); + instrTest.addInstrumentationArg( + APP_LAUNCH_TIMEOUT_LABEL, Integer.toString(mAppLaunchTimeoutMs)); + instrTest.addInstrumentationArg( + WORKSPACE_LAUNCH_TIMEOUT_LABEL, Integer.toString(mWorkspaceLaunchTimeoutMs)); + return instrTest; + } +} diff --git a/harness/src/main/java/com/android/compatibility/targetprep/AppSetupPreparer.java b/harness/src/main/java/com/android/compatibility/targetprep/AppSetupPreparer.java index 12e30f7..b742c2f 100644 --- a/harness/src/main/java/com/android/compatibility/targetprep/AppSetupPreparer.java +++ b/harness/src/main/java/com/android/compatibility/targetprep/AppSetupPreparer.java @@ -17,230 +17,99 @@ package com.android.compatibility.targetprep; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; -import com.android.csuite.core.SystemPackageUninstaller; import com.android.tradefed.build.IBuildInfo; -import com.android.tradefed.config.ConfigurationException; import com.android.tradefed.config.Option; -import com.android.tradefed.config.OptionSetter; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.invoker.TestInformation; -import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.targetprep.BuildError; import com.android.tradefed.targetprep.ITargetPreparer; import com.android.tradefed.targetprep.TargetSetupError; import com.android.tradefed.targetprep.TestAppInstallSetup; -import com.android.tradefed.util.AaptParser.AaptVersion; import com.google.common.annotations.VisibleForTesting; -import com.google.common.util.concurrent.SimpleTimeLimiter; -import com.google.common.util.concurrent.TimeLimiter; -import com.google.common.util.concurrent.UncheckedTimeoutException; import java.io.File; -import java.time.Duration; -import java.util.ArrayList; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** A Tradefed preparer that downloads and installs an app on the target device. */ public final class AppSetupPreparer implements ITargetPreparer { - @VisibleForTesting - static final String OPTION_WAIT_FOR_DEVICE_AVAILABLE_SECONDS = - "wait-for-device-available-seconds"; - - @VisibleForTesting - static final String OPTION_EXPONENTIAL_BACKOFF_MULTIPLIER_SECONDS = - "exponential-backoff-multiplier-seconds"; + public static final String OPTION_GCS_APK_DIR = "gcs-apk-dir"; - @VisibleForTesting static final String OPTION_TEST_FILE_NAME = "test-file-name"; - @VisibleForTesting static final String OPTION_INSTALL_ARG = "install-arg"; - @VisibleForTesting static final String OPTION_SETUP_TIMEOUT_MILLIS = "setup-timeout-millis"; - @VisibleForTesting static final String OPTION_MAX_RETRY = "max-retry"; - @VisibleForTesting static final String OPTION_AAPT_VERSION = "aapt-version"; - @VisibleForTesting static final String OPTION_INCREMENTAL_INSTALL = "incremental"; - - @Option(name = "package-name", description = "Package name of testing app.") + @Option(name = "package-name", description = "Package name of the app being tested.") private String mPackageName; - @Option( - name = OPTION_TEST_FILE_NAME, - description = "the name of an apk file to be installed on device. Can be repeated.") - private final List<File> mTestFiles = new ArrayList<>(); - - @Option(name = OPTION_AAPT_VERSION, description = "The version of AAPT for APK parsing.") - private AaptVersion mAaptVersion = AaptVersion.AAPT2; - - @Option( - name = OPTION_INSTALL_ARG, - description = - "Additional arguments to be passed to install command, " - + "including leading dash, e.g. \"-d\"") - private final List<String> mInstallArgs = new ArrayList<>(); - - @Option( - name = OPTION_INCREMENTAL_INSTALL, - description = "Enable packages to be installed incrementally.") - private boolean mIncrementalInstallation = false; - - @Option(name = OPTION_MAX_RETRY, description = "Max number of retries upon TargetSetupError.") - private int mMaxRetry = 0; - - @Option( - name = OPTION_EXPONENTIAL_BACKOFF_MULTIPLIER_SECONDS, - description = - "The exponential backoff multiplier for retries in seconds. " - + "A value n means the preparer will wait for n^(retry_count) " - + "seconds between retries.") - private int mExponentialBackoffMultiplierSeconds = 0; - - @Option( - name = OPTION_WAIT_FOR_DEVICE_AVAILABLE_SECONDS, - description = - "Timeout value for waiting for device available in seconds. " - + "A negative value means not to check device availability.") - private int mWaitForDeviceAvailableSeconds = -1; - - @Option( - name = OPTION_SETUP_TIMEOUT_MILLIS, - description = - "Timeout value for a setUp operation. " - + "Note that the timeout is not a global timeout and will " - + "be applied to each retry attempt.") - private long mSetupOnceTimeoutMillis = TimeUnit.MINUTES.toMillis(10); - - private final TestAppInstallSetup mTestAppInstallSetup; - private final Sleeper mSleeper; - private final TimeLimiter mTimeLimiter = - SimpleTimeLimiter.create(Executors.newCachedThreadPool()); + private final TestAppInstallSetup mAppInstallSetup; public AppSetupPreparer() { - this(new TestAppInstallSetup(), Sleepers.DefaultSleeper.INSTANCE); + this(null, new TestAppInstallSetup()); } @VisibleForTesting - public AppSetupPreparer(TestAppInstallSetup testAppInstallSetup, Sleeper sleeper) { - mTestAppInstallSetup = testAppInstallSetup; - mSleeper = sleeper; + public AppSetupPreparer(String packageName, TestAppInstallSetup appInstallSetup) { + this.mPackageName = packageName; + this.mAppInstallSetup = appInstallSetup; } /** {@inheritDoc} */ @Override public void setUp(ITestDevice device, IBuildInfo buildInfo) throws DeviceNotAvailableException, BuildError, TargetSetupError { - checkArgumentNonNegative(mMaxRetry, OPTION_MAX_RETRY); - checkArgumentNonNegative( - mExponentialBackoffMultiplierSeconds, - OPTION_EXPONENTIAL_BACKOFF_MULTIPLIER_SECONDS); - checkArgumentNonNegative(mSetupOnceTimeoutMillis, OPTION_SETUP_TIMEOUT_MILLIS); - - int runCount = 0; - while (true) { - TargetSetupError currentException; - try { - runCount++; - - ITargetPreparer handler = - mTimeLimiter.newProxy( - new ITargetPreparer() { - @Override - public void setUp(ITestDevice device, IBuildInfo buildInfo) - throws DeviceNotAvailableException, BuildError, - TargetSetupError { - setUpOnce(device, buildInfo); - } - }, - ITargetPreparer.class, - mSetupOnceTimeoutMillis, - TimeUnit.MILLISECONDS); - handler.setUp(device, buildInfo); - - break; - } catch (TargetSetupError e) { - currentException = e; - } catch (UncheckedTimeoutException e) { - currentException = new TargetSetupError(e.getMessage(), e); - } - - waitForDeviceAvailable(device); - if (runCount > mMaxRetry) { - throw currentException; - } - CLog.w("setUp failed: %s. Run count: %d. Retrying...", currentException, runCount); - - try { - mSleeper.sleep( - Duration.ofSeconds( - (int) Math.pow(mExponentialBackoffMultiplierSeconds, runCount))); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new TargetSetupError(e.getMessage(), e); - } - } - } + // TODO(b/147159584): Use a utility to get dynamic options. + String gcsApkDirOption = buildInfo.getBuildAttributes().get(OPTION_GCS_APK_DIR); + checkNotNull(gcsApkDirOption, "Option %s is not set.", OPTION_GCS_APK_DIR); - private void setUpOnce(ITestDevice device, IBuildInfo buildInfo) - throws DeviceNotAvailableException, BuildError, TargetSetupError { - mTestAppInstallSetup.setAaptVersion(mAaptVersion); + File apkDir = new File(gcsApkDirOption); + checkArgument( + apkDir.isDirectory(), + String.format("GCS Apk Directory %s is not a directory", apkDir)); - try { - OptionSetter setter = new OptionSetter(mTestAppInstallSetup); - setter.setOptionValue("incremental", String.valueOf(mIncrementalInstallation)); - } catch (ConfigurationException e) { - throw new TargetSetupError(e.getMessage(), e); - } + File packageDir = new File(apkDir.getPath(), mPackageName); + checkArgument( + packageDir.isDirectory(), + String.format("Package directory %s is not a directory", packageDir)); - if (mPackageName != null) { - SystemPackageUninstaller.uninstallPackage(mPackageName, device); + mAppInstallSetup.setAltDir(packageDir); + + List<String> apkFilePaths; + try { + apkFilePaths = listApkFilePaths(packageDir); + } catch (IOException e) { + throw new TargetSetupError( + String.format("Failed to access files in %s.", packageDir), e); } - for (File testFile : mTestFiles) { - mTestAppInstallSetup.addTestFile(testFile); + if (apkFilePaths.isEmpty()) { + throw new TargetSetupError( + String.format("Failed to find apk files in %s.", packageDir)); } - for (String installArg : mInstallArgs) { - mTestAppInstallSetup.addInstallArg(installArg); + if (apkFilePaths.size() == 1) { + mAppInstallSetup.addTestFileName(apkFilePaths.get(0)); + } else { + mAppInstallSetup.addSplitApkFileNames(String.join(",", apkFilePaths)); } - mTestAppInstallSetup.setUp(device, buildInfo); + mAppInstallSetup.setUp(device, buildInfo); } /** {@inheritDoc} */ @Override public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException { - mTestAppInstallSetup.tearDown(testInfo, e); + mAppInstallSetup.tearDown(testInfo, e); } - private void waitForDeviceAvailable(ITestDevice device) throws DeviceNotAvailableException { - if (mWaitForDeviceAvailableSeconds < 0) { - return; - } - - device.waitForDeviceAvailable(1000L * mWaitForDeviceAvailableSeconds); - } - - private void checkArgumentNonNegative(long val, String name) { - checkArgument(val >= 0, "%s (%s) must not be negative", name, val); - } - - @VisibleForTesting - interface Sleeper { - void sleep(Duration duration) throws InterruptedException; - } - - static class Sleepers { - enum DefaultSleeper implements Sleeper { - INSTANCE; - - @Override - public void sleep(Duration duration) throws InterruptedException { - Thread.sleep(duration.toMillis()); - } - } - - private Sleepers() {} + private List<String> listApkFilePaths(File downloadDir) throws IOException { + return Files.walk(Paths.get(downloadDir.getPath())) + .map(x -> x.getFileName().toString()) + .filter(s -> s.endsWith(".apk")) + .collect(Collectors.toList()); } } diff --git a/harness/src/main/java/com/android/compatibility/targetprep/AppSetupPreparerConfigurationReceiver.java b/harness/src/main/java/com/android/compatibility/targetprep/AppSetupPreparerConfigurationReceiver.java new file mode 100644 index 0000000..7e50f3f --- /dev/null +++ b/harness/src/main/java/com/android/compatibility/targetprep/AppSetupPreparerConfigurationReceiver.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compatibility.targetprep; + +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.targetprep.ITargetPreparer; + +import com.google.common.annotations.VisibleForTesting; + +import java.io.File; + +/** + * A Tradefed preparer that receives module preparer options and stores the values in IBuildInfo. + */ +public final class AppSetupPreparerConfigurationReceiver implements ITargetPreparer { + + @Option( + name = AppSetupPreparer.OPTION_GCS_APK_DIR, + description = "GCS path where the test apk files are located.") + private File mOptionGcsApkDir; + + public AppSetupPreparerConfigurationReceiver() { + this(null); + } + + @VisibleForTesting + public AppSetupPreparerConfigurationReceiver(File optionGcsApkDir) { + mOptionGcsApkDir = optionGcsApkDir; + } + + /** {@inheritDoc} */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) { + if (mOptionGcsApkDir == null) { + return; + } + buildInfo.addBuildAttribute( + AppSetupPreparer.OPTION_GCS_APK_DIR, mOptionGcsApkDir.getPath()); + } +} diff --git a/harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java b/harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java deleted file mode 100644 index 8d40d04..0000000 --- a/harness/src/main/java/com/android/compatibility/targetprep/CheckGmsPreparer.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.compatibility.targetprep; - -import com.android.tradefed.config.Option; -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.invoker.TestInformation; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.targetprep.ITargetPreparer; -import com.android.tradefed.targetprep.TargetSetupError; -import com.android.tradefed.util.CommandResult; - -import com.google.common.annotations.VisibleForTesting; - -/** - * Checks and recover GMS on a device. - * - * <p>This preparer checks whether the GMS process is running during setUp and tearDown. If GMS is - * not running before the test, a reboot will be attempted to recover. - */ -public final class CheckGmsPreparer implements ITargetPreparer { - - private static final long WAIT_FOR_BOOT_COMPLETE_TIMEOUT_MILLIS = 1000 * 60; - @VisibleForTesting static final String CHECK_GMS_COMMAND = "pidof com.google.android.gms"; - @VisibleForTesting static final String OPTION_ENABLE = "enable"; - - @Option(name = OPTION_ENABLE, description = "Enable GMS checks.") - protected boolean mEnable = false; - - /** {@inheritDoc} */ - @Override - public void setUp(TestInformation testInfo) - throws TargetSetupError, DeviceNotAvailableException { - if (!mEnable || isGmsRunning(testInfo)) { - return; - } - - CLog.e("Did not detect a running GMS process, rebooting device to recover"); - testInfo.getDevice().reboot(); - testInfo.getDevice().waitForBootComplete(WAIT_FOR_BOOT_COMPLETE_TIMEOUT_MILLIS); - - if (!isGmsRunning(testInfo)) { - CLog.e("GMS process still not running, throwing error"); - mEnable = false; - throw new TargetSetupError( - "GMS required but did not detect a running GMS process after device reboot"); - } - } - - private static boolean isGmsRunning(TestInformation testInfo) - throws DeviceNotAvailableException { - CommandResult res = testInfo.getDevice().executeShellV2Command(CHECK_GMS_COMMAND); - if (res.getExitCode() == 0) { - CLog.d("Detected a running GMS process with PID: %s", res.getStdout()); - return true; - } - - CLog.e( - "Check GMS command returned non zero exit code. Command: %s, Result: %s", - CHECK_GMS_COMMAND, res.toString()); - return false; - } - - /** {@inheritDoc} */ - @Override - public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException { - if (!mEnable || isGmsRunning(testInfo)) { - return; - } - - CLog.e("Did not detect a running GMS process on tearDown"); - } -} diff --git a/harness/src/main/java/com/android/compatibility/testtype/AppLaunchTest.java b/harness/src/main/java/com/android/compatibility/testtype/AppLaunchTest.java index 7a52735..fb34dbf 100644 --- a/harness/src/main/java/com/android/compatibility/testtype/AppLaunchTest.java +++ b/harness/src/main/java/com/android/compatibility/testtype/AppLaunchTest.java @@ -59,12 +59,6 @@ import java.util.Set; /** A test that verifies that a single app can be successfully launched. */ public class AppLaunchTest implements IDeviceTest, IRemoteTest, IConfigurationReceiver, ITestFilterReceiver { - @VisibleForTesting static final String SCREENSHOT_AFTER_LAUNCH = "screenshot-after-launch"; - - @Option( - name = SCREENSHOT_AFTER_LAUNCH, - description = "Whether to take a screenshost after a package is launched.") - private boolean mScreenshotAfterLaunch; @Option(name = "package-name", description = "Package name of testing app.") private String mPackageName; @@ -85,9 +79,6 @@ public class AppLaunchTest @Option(name = "exclude-filter", description = "The exclude filter of the test type.") protected Set<String> mExcludeFilters = new HashSet<>(); - @Option(name = "dismiss-dialog", description = "Attempt to dismiss dialog from apps.") - protected boolean mDismissDialog = false; - @Option( name = "app-launch-timeout-ms", description = "Time to wait for app to launch in msecs.") @@ -97,22 +88,18 @@ public class AppLaunchTest "com.android.compatibilitytest.AppCompatibilityRunner"; private static final String LAUNCH_TEST_PACKAGE = "com.android.compatibilitytest"; private static final String PACKAGE_TO_LAUNCH = "package_to_launch"; - private static final String ARG_DISMISS_DIALOG = "ARG_DISMISS_DIALOG"; private static final String APP_LAUNCH_TIMEOUT_LABEL = "app_launch_timeout_ms"; private static final int LOGCAT_SIZE_BYTES = 20 * 1024 * 1024; - private static final int BASE_INSTRUMENTATION_TEST_TIMEOUT_MS = 10 * 1000; private ITestDevice mDevice; private LogcatReceiver mLogcat; private IConfiguration mConfiguration; - public AppLaunchTest() { - this(null); - } + public AppLaunchTest() {} @VisibleForTesting public AppLaunchTest(String packageName) { - this(packageName, 0); + mPackageName = packageName; } @VisibleForTesting @@ -126,23 +113,17 @@ public class AppLaunchTest * the package being tested (provided as a parameter). */ protected InstrumentationTest createInstrumentationTest(String packageBeingTested) { - InstrumentationTest instrumentationTest = new InstrumentationTest(); - - instrumentationTest.setPackageName(LAUNCH_TEST_PACKAGE); - instrumentationTest.setConfiguration(mConfiguration); - instrumentationTest.addInstrumentationArg(PACKAGE_TO_LAUNCH, packageBeingTested); - instrumentationTest.setRunnerName(LAUNCH_TEST_RUNNER); - instrumentationTest.setDevice(mDevice); - instrumentationTest.addInstrumentationArg( + InstrumentationTest instrTest = new InstrumentationTest(); + + instrTest.setPackageName(LAUNCH_TEST_PACKAGE); + instrTest.setConfiguration(mConfiguration); + instrTest.addInstrumentationArg(PACKAGE_TO_LAUNCH, packageBeingTested); + instrTest.setRunnerName(LAUNCH_TEST_RUNNER); + instrTest.setDevice(mDevice); + instrTest.addInstrumentationArg( APP_LAUNCH_TIMEOUT_LABEL, Integer.toString(mAppLaunchTimeoutMs)); - instrumentationTest.addInstrumentationArg( - ARG_DISMISS_DIALOG, Boolean.toString(mDismissDialog)); - int testTimeoutMs = BASE_INSTRUMENTATION_TEST_TIMEOUT_MS + mAppLaunchTimeoutMs * 2; - instrumentationTest.setShellTimeout(testTimeoutMs); - instrumentationTest.setTestTimeout(testTimeoutMs); - - return instrumentationTest; + return instrTest; } /* @@ -207,21 +188,7 @@ public class AppLaunchTest // Clear test result between retries. launchPackage(testInfo, result); if (result.status == CompatibilityTestResult.STATUS_SUCCESS) { - break; - } - } - - if (mScreenshotAfterLaunch) { - try (InputStreamSource screenSource = mDevice.getScreenshot()) { - listener.testLog( - mPackageName + "_screenshot_" + mDevice.getSerialNumber(), - LogDataType.PNG, - screenSource); - } catch (DeviceNotAvailableException e) { - CLog.e( - "Device %s became unavailable while capturing screenshot, %s", - mDevice.getSerialNumber(), e.toString()); - throw e; + return; } } } finally { diff --git a/harness/src/main/java/com/android/csuite/config/AppRemoteFileResolver.java b/harness/src/main/java/com/android/csuite/config/AppRemoteFileResolver.java deleted file mode 100644 index 82fddb8..0000000 --- a/harness/src/main/java/com/android/csuite/config/AppRemoteFileResolver.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.csuite.config; - -import com.android.tradefed.build.BuildRetrievalError; -import com.android.tradefed.config.ConfigurationException; -import com.android.tradefed.config.DynamicRemoteFileResolver; -import com.android.tradefed.config.Option; -import com.android.tradefed.config.OptionClass; -import com.android.tradefed.config.OptionSetter; -import com.android.tradefed.config.remote.IRemoteFileResolver; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.base.Stopwatch; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; - -import java.io.File; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Map; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.NotThreadSafe; - -/** - * An implementation of {@code IRemoteFileResolver} for downloading Android apps. - * - * <p>The scheme supported by this resolver allows Trade Federation test configs to abstract the - * actual service used to download Android app APK files. Note that this is a 'meta' resolver that - * resolves abstract 'app://' URIs into a URI with a different scheme using a custom template. The - * actual downloading of this resolved URI is then delegated to another registered {@link - * IRemoteFileResolver} implementation. Variable placeholders in the URI template string are - * expanded with corresponding values. - * - * <h2>Syntax and usage</h2> - * - * <p>References to apps in TradeFed test configs must have the following syntax: - * - * <blockquote> - * - * <b>{@code app://}</b><i>package-name</i> - * - * </blockquote> - * - * where <i>package-name</i> is the name of the application package such as: - * - * <blockquote> - * - * <table cellpadding=0 cellspacing=0 summary="layout"> - * <tr><td>{@code app://com.example.myapp}<td></tr> - * </table> - * - * </blockquote> - * - * App APK files are downloaded to a directory and must be used in contexts that can handle File - * objects pointing to directories. - * - * <h2>Configuration</h2> - * - * <p>The URI template to use is specified using the {@code dynamic-download-args} TradeFed - * command-line argument: - * - * <blockquote> - * - * <pre> - * --dynamic-download-args app:uri-template=file:///app_files/{package} - * </pre> - * - * </blockquote> - * - * <p>Where {package} expands to the actual package name being downloaded. Any illegal URI - * characters must also be properly escaped as expected by {@link java.net.URI}. - * - * <p><span style="font-weight: bold; padding-right: 1em">Usage Note:</span> The {@code - * --enable-module-dynamic-download} flag must be set to {@code true} when used in test suites. - * - * @see com.android.tradefed.config.Option - */ -@NotThreadSafe -@OptionClass(alias = "app") -public final class AppRemoteFileResolver implements IRemoteFileResolver { - - private static final String URI_SCHEME = "app"; - private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(\\w+)\\}"); - - @VisibleForTesting static final String URI_TEMPLATE_OPTION = "uri-template"; - - @Option(name = URI_TEMPLATE_OPTION) - private String mUriTemplate; - - @Nullable private ITestDevice mPrimaryDevice; - - @Override - public String getSupportedProtocol() { - return URI_SCHEME; - } - - @Override - public void setPrimaryDevice(@Nullable ITestDevice primaryDevice) { - this.mPrimaryDevice = primaryDevice; - } - - @Override - public File resolveRemoteFiles(File uriSchemeAndPathAsFile) throws BuildRetrievalError { - // Note that this method is not really supported or even called by the framework. We - // only override it to simplify automated null pointer testing. - return resolveRemoteFiles(uriSchemeAndPathAsFile, ImmutableMap.of()); - } - - @Override - public File resolveRemoteFiles( - File uriSchemeAndPathAsFile, Map<String, String> uriQueryAndExtraParameters) - throws BuildRetrievalError { - URI appUri = checkAppUri(toUri(uriSchemeAndPathAsFile)); - Objects.requireNonNull(uriQueryAndExtraParameters); - - // TODO(hzalek): Remove this and make the corresponding option mandatory once test configs - // are using app URIs. - if (mUriTemplate == null) { - CLog.w("Resolver is not properly configured, skipping resolution of URI (%s)", appUri); - return null; - } - - Preconditions.checkState( - !mUriTemplate.isEmpty(), - String.format("%s=%s is empty", URI_TEMPLATE_OPTION, mUriTemplate)); - - String packageName = appUri.getAuthority(); - String expanded = expandVars(mUriTemplate, ImmutableMap.of("package", packageName)); - - URI uri; - try { - uri = new URI(expanded); - } catch (URISyntaxException e) { - throw new IllegalStateException( - String.format( - "URI template (%s) did not expand to a a valid URI (%s)", - URI_TEMPLATE_OPTION, mUriTemplate, expanded), - e); - } - - if (URI_SCHEME.equals(uri.getScheme())) { - throw new BuildRetrievalError( - String.format( - "Providers must return URIs with a scheme different than '%s': %s > %s", - URI_SCHEME, appUri, uri)); - } - - return resolveUriToFile(packageName, uri, uriQueryAndExtraParameters); - } - - private static URI toUri(File uriSchemeAndPathAsFile) { - try { - // TradeFed forces a URI into a File instance which is lossy and forces us to attempt - // restoring the original format here so we don't have to use regular expressions. Be - // warned that using getAbsolutePath() will incorrectly strip the scheme. - String path = uriSchemeAndPathAsFile.getPath(); - // Restore the original URI form since the first two forward slashes in the URI string - // get normalized into one when stored as a file. - path = path.replaceFirst(":/", "://"); - return new URI(path); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Could not parse provided URI", e); - } - } - - private static URI checkAppUri(URI uri) { - String uriScheme = uri.getScheme(); - if (!URI_SCHEME.equals(uriScheme)) { - throw new IllegalArgumentException( - String.format("Unsupported scheme (%s) in provided URI (%s)", uriScheme, uri)); - } - - // Note that the below code accesses the 'authority' component of the URI and not 'path' - // like the dynamic resolver implementation. The latter has to do so because the authority - // component is no longer defined once the '//' gets converted to a single '/'. - String packageName = uri.getAuthority(); - if (Strings.isNullOrEmpty(packageName)) { - throw new IllegalArgumentException( - String.format( - "Invalid package name (%s) in provided URI (%s)", packageName, uri)); - } - - if (!Strings.isNullOrEmpty(uri.getPath())) { - throw new IllegalArgumentException( - String.format( - "Path component (%s) incorrectly specified in provided URI (%s); " - + "app URIs must be of the form 'app://com.example.app'", - uri.getPath(), uri)); - } - - return uri; - } - - private static String expandVars(CharSequence template, Map<String, String> vars) { - StringBuilder sb = new StringBuilder(); - Matcher matcher = PLACEHOLDER_PATTERN.matcher(template); - int position = 0; - - while (matcher.find()) { - sb.append(template.subSequence(position, matcher.start(0))); - - String varName = matcher.group(1); - String varValue = vars.get(varName); - - if (varValue == null) { - throw new IllegalStateException( - String.format( - "URI template (%s) contains a placeholder for undefined var (%s)", - template, varName)); - } - - sb.append(varValue); - position = matcher.end(0); - } - - sb.append(template.subSequence(position, template.length())); - String expanded = sb.toString(); - - CLog.i("Template (%s) expanded (%s) using vars (%s)", template, expanded, vars); - return expanded; - } - - private File resolveUriToFile(String packageName, URI uri, Map<String, String> params) - throws BuildRetrievalError { - DynamicRemoteFileResolver resolver = new DynamicRemoteFileResolver(); - resolver.setDevice(mPrimaryDevice); - resolver.addExtraArgs(params); - - FileOptionSource optionSource = new FileOptionSource(); - Stopwatch stopwatch = Stopwatch.createStarted(); - - try { - OptionSetter setter = new OptionSetter(optionSource); - setter.setOptionValue(FileOptionSource.OPTION_NAME, uri.toString()); - setter.validateRemoteFilePath(resolver); - CLog.i("Resolution of files took %d ms", stopwatch.elapsed().toMillis()); - } catch (BuildRetrievalError e) { - throw new BuildRetrievalError( - String.format("Could not resolve URI (%s) for package '%s'", uri, packageName), - e); - } catch (ConfigurationException impossible) { - throw new AssertionError(impossible); - } - - if (!optionSource.file.exists()) { - CLog.w("URI (%s) resolved to non-existent local file (%s)", uri, optionSource.file); - } else { - CLog.i("URI (%s) resolved to local file (%s)", uri, optionSource.file); - } - - return optionSource.file; - } - - /** This is required to resolve URIs since the remote resolver only deals with options. */ - private static final class FileOptionSource { - static final String OPTION_NAME = "file"; - - @Option(name = OPTION_NAME, mandatory = true) - public File file; - } -} diff --git a/harness/src/main/java/com/android/csuite/config/ModuleGenerator.java b/harness/src/main/java/com/android/csuite/config/ModuleGenerator.java deleted file mode 100644 index 0760085..0000000 --- a/harness/src/main/java/com/android/csuite/config/ModuleGenerator.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.csuite.config; - -import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; -import com.android.csuite.core.PackageNameProvider; -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.Option.Importance; -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.invoker.TestInformation; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.result.ITestInvocationListener; -import com.android.tradefed.targetprep.ITargetPreparer; -import com.android.tradefed.testtype.IBuildReceiver; -import com.android.tradefed.testtype.IRemoteTest; -import com.android.tradefed.testtype.IShardableTest; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.io.Resources; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -/** - * A tool for generating TradeFed suite modules during runtime. - * - * <p>This class generates module config files into TradeFed's test directory at runtime using a - * template. Since the content of the test directory relies on what is being generated in a test - * run, there can only be one instance executing at a given time. - * - * <p>The intention of this class is to generate test modules at the beginning of a test run and - * cleans up after all tests finish, which resembles a target preparer. However, a target preparer - * is executed after the sharding process has finished. The only way to make the generated modules - * available for sharding without making changes to TradeFed's core code is to disguise this module - * generator as an instance of IShardableTest and declare it separately in test plan config. This is - * hacky, and in the long term a TradeFed centered solution is desired. For more details, see - * go/sharding-hack-for-module-gen. Note that since the generate step is executed as a test instance - * and cleanup step is executed as a target preparer, there should be no saved states between - * generating and cleaning up module files. - * - * <p>This module generator collects package names from all PackageNameProvider objects specified in - * the test configs. - * - * <h2>Syntax and usage</h2> - * - * <p>References to package name providers in TradeFed test configs must have the following syntax: - * - * <blockquote> - * - * <b><object type="PACKAGE_NAME_PROVIDER" class="</b><i>provider_class_name</i><b>"/></b> - * - * </blockquote> - * - * where <i>provider_class_name</i> is the fully-qualified class name of an PackageNameProvider - * implementation class. - */ -public final class ModuleGenerator - implements IRemoteTest, - IShardableTest, - IBuildReceiver, - ITargetPreparer, - IConfigurationReceiver { - - @VisibleForTesting static final String MODULE_FILE_EXTENSION = ".config"; - @VisibleForTesting static final String OPTION_TEMPLATE = "template"; - @VisibleForTesting static final String PACKAGE_NAME_PROVIDER = "PACKAGE_NAME_PROVIDER"; - private static final String TEMPLATE_PACKAGE_PATTERN = "\\{package\\}"; - private static final Collection<IRemoteTest> NOT_SPLITABLE = null; - - @Option( - name = OPTION_TEMPLATE, - description = "Module config template resource path.", - importance = Importance.ALWAYS) - private String mTemplate; - - private final TestDirectoryProvider mTestDirectoryProvider; - private final ResourceLoader mResourceLoader; - private final FileSystem mFileSystem; - private IBuildInfo mBuildInfo; - private IConfiguration mConfiguration; - - @Override - public void setConfiguration(IConfiguration configuration) { - mConfiguration = configuration; - } - - public ModuleGenerator() { - this(FileSystems.getDefault()); - } - - private ModuleGenerator(FileSystem fileSystem) { - this( - fileSystem, - new CompatibilityTestDirectoryProvider(fileSystem), - new ClassResourceLoader()); - } - - @VisibleForTesting - ModuleGenerator( - FileSystem fileSystem, - TestDirectoryProvider testDirectoryProvider, - ResourceLoader resourceLoader) { - mFileSystem = fileSystem; - mTestDirectoryProvider = testDirectoryProvider; - mResourceLoader = resourceLoader; - } - - @Override - public void run(final TestInformation testInfo, final ITestInvocationListener listener) { - // Intentionally left blank since this class is not really a test. - } - - @Override - public void setUp(TestInformation testInfo) { - // Intentionally left blank. - } - - @Override - public void setBuild(IBuildInfo buildInfo) { - mBuildInfo = buildInfo; - } - - /** - * Generates test modules. Note that the implementation of this method is not related to - * sharding in any way. - */ - @Override - public Collection<IRemoteTest> split() { - try { - // Executes the generate step. - generateModules(); - } catch (IOException e) { - throw new UncheckedIOException("Failed to generate modules", e); - } - - return NOT_SPLITABLE; - } - - /** Cleans up generated test modules. */ - @Override - public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException { - // Gets build info from test info as when the class is executed as a ITargetPreparer - // preparer, it is not considered as a IBuildReceiver instance. - mBuildInfo = testInfo.getBuildInfo(); - - try { - // Executes the clean up step. - cleanUpModules(); - } catch (IOException ioException) { - throw new UncheckedIOException("Failed to clean up generated modules", ioException); - } - } - - private Set<String> getPackageNames() throws IOException { - Set<String> packages = new HashSet<>(); - for (Object provider : mConfiguration.getConfigurationObjectList(PACKAGE_NAME_PROVIDER)) { - packages.addAll(((PackageNameProvider) provider).get()); - } - return packages; - } - - private void generateModules() throws IOException { - String templateContent = mResourceLoader.load(mTemplate); - - for (String packageName : getPackageNames()) { - validatePackageName(packageName); - Files.write( - getModulePath(packageName), - templateContent.replaceAll(TEMPLATE_PACKAGE_PATTERN, packageName).getBytes()); - } - } - - private void cleanUpModules() throws IOException { - getPackageNames() - .forEach( - packageName -> { - try { - Files.delete(getModulePath(packageName)); - } catch (IOException ioException) { - CLog.e( - "Failed to delete the generated module for package " - + packageName, - ioException); - } - }); - } - - private Path getModulePath(String packageName) throws IOException { - Path testsDir = mTestDirectoryProvider.get(mBuildInfo); - return testsDir.resolve(packageName + MODULE_FILE_EXTENSION); - } - - private static void validatePackageName(String packageName) { - if (packageName.isEmpty() || packageName.matches(".*" + TEMPLATE_PACKAGE_PATTERN + ".*")) { - throw new IllegalArgumentException( - "Package name cannot be empty or contains package placeholder: " - + TEMPLATE_PACKAGE_PATTERN); - } - } - - @VisibleForTesting - interface ResourceLoader { - String load(String resourceName) throws IOException; - } - - private static final class ClassResourceLoader implements ResourceLoader { - @Override - public String load(String resourceName) throws IOException { - return Resources.toString( - getClass().getClassLoader().getResource(resourceName), StandardCharsets.UTF_8); - } - } - - @VisibleForTesting - interface TestDirectoryProvider { - Path get(IBuildInfo buildInfo) throws IOException; - } - - private static final class CompatibilityTestDirectoryProvider implements TestDirectoryProvider { - private final FileSystem mFileSystem; - - private CompatibilityTestDirectoryProvider(FileSystem fileSystem) { - mFileSystem = fileSystem; - } - - @Override - public Path get(IBuildInfo buildInfo) throws IOException { - return mFileSystem.getPath( - new CompatibilityBuildHelper(buildInfo).getTestsDir().getPath()); - } - } -} diff --git a/harness/src/main/java/com/android/csuite/core/CommandLinePackageNameProvider.java b/harness/src/main/java/com/android/csuite/core/CommandLinePackageNameProvider.java deleted file mode 100644 index 33426ed..0000000 --- a/harness/src/main/java/com/android/csuite/core/CommandLinePackageNameProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 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.csuite.core; - -import com.android.tradefed.config.Option; -import com.android.tradefed.config.OptionClass; - -import com.google.common.annotations.VisibleForTesting; - -import java.util.HashSet; -import java.util.Set; - -/** A package name provider that accepts package names via a command line option. */ -@OptionClass(alias = "command-line-package-name-provider") -public final class CommandLinePackageNameProvider implements PackageNameProvider { - @VisibleForTesting static final String PACKAGE = "package"; - - @Option(name = PACKAGE, description = "App package names.") - private final Set<String> mPackages = new HashSet<>(); - - @Override - public Set<String> get() { - return mPackages; - } -} diff --git a/harness/src/main/java/com/android/csuite/core/FileBasedPackageNameProvider.java b/harness/src/main/java/com/android/csuite/core/FileBasedPackageNameProvider.java deleted file mode 100644 index 6f6af06..0000000 --- a/harness/src/main/java/com/android/csuite/core/FileBasedPackageNameProvider.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2021 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.csuite.core; - -import com.android.tradefed.config.Option; - -import com.google.common.annotations.VisibleForTesting; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; - -/** A package name provider that accepts files that contains package names. */ -public final class FileBasedPackageNameProvider implements PackageNameProvider { - @VisibleForTesting static final String PACKAGES_FILE = "packages-file"; - @VisibleForTesting static final String COMMENT_LINE_PREFIX = "#"; - - @Option( - name = PACKAGES_FILE, - description = - "File paths that contain package names separated by newline characters." - + " Comment lines are supported only if the lines start with double slash." - + " Trailing comments are not supported. Empty lines are ignored.") - private final Set<File> mPackagesFiles = new HashSet<>(); - - @Override - public Set<String> get() throws IOException { - Set<String> packages = new HashSet<>(); - for (File packagesFile : mPackagesFiles) { - packages.addAll( - Files.readAllLines(packagesFile.toPath()).parallelStream() - .map(String::trim) - .filter(this::isPackageName) - .collect(Collectors.toSet())); - } - return packages; - } - - private boolean isPackageName(String text) { - // Check the text is not an empty string and not a comment line. - return !text.isEmpty() && !text.startsWith(COMMENT_LINE_PREFIX); - } -} diff --git a/harness/src/main/java/com/android/csuite/core/PackageNameProvider.java b/harness/src/main/java/com/android/csuite/core/PackageNameProvider.java deleted file mode 100644 index 05709a1..0000000 --- a/harness/src/main/java/com/android/csuite/core/PackageNameProvider.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2021 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.csuite.core; - -import java.io.IOException; -import java.util.Set; - -/** Provides a list of package names. */ -public interface PackageNameProvider { - /** - * Returns a set of package names. - * - * @return the package names. An empty set is returned if no package names are to be provided. - * @throws IOException if failed to get package names. - */ - Set<String> get() throws IOException; -} diff --git a/harness/src/main/java/com/android/csuite/core/SystemPackageUninstaller.java b/harness/src/main/java/com/android/csuite/core/SystemPackageUninstaller.java deleted file mode 100644 index 4ed6efe..0000000 --- a/harness/src/main/java/com/android/csuite/core/SystemPackageUninstaller.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.csuite.core; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.targetprep.TargetSetupError; -import com.android.tradefed.util.CommandResult; -import com.android.tradefed.util.CommandStatus; - -import com.google.common.annotations.VisibleForTesting; - -import java.nio.file.Paths; -import java.util.Arrays; - -/** - * Uninstalls a system app. - * - * <p>This utility class may not restore the uninstalled system app after test completes. - * - * <p>The class may disable dm verity on some devices, and it does not re-enable it after - * uninstalling a system app. - */ -public final class SystemPackageUninstaller { - @VisibleForTesting static final String OPTION_PACKAGE_NAME = "package-name"; - static final String SYSPROP_DEV_BOOTCOMPLETE = "dev.bootcomplete"; - static final String SYSPROP_SYS_BOOT_COMPLETED = "sys.boot_completed"; - static final long WAIT_FOR_BOOT_COMPLETE_TIMEOUT_MILLIS = 1000 * 60; - @VisibleForTesting static final int MAX_NUMBER_OF_UPDATES = 100; - @VisibleForTesting static final String PM_CHECK_COMMAND = "pm path android"; - - public static void uninstallPackage(String packageName, ITestDevice device) - throws TargetSetupError, DeviceNotAvailableException { - checkNotNull(packageName); - - if (!isPackageManagerRunning(device)) { - CLog.w( - "Package manager is not available on the device." - + " Attempting to recover it by restarting the framework."); - runAsRoot( - device, - () -> { - stopFramework(device); - startFramework(device); - }); - if (!isPackageManagerRunning(device)) { - throw new TargetSetupError("The package manager failed to start."); - } - } - - if (!isPackageInstalled(packageName, device)) { - CLog.i("Package %s is not installed.", packageName); - return; - } - - // Attempts to uninstall the package/updates from user partition. - // This method should be called before the other methods and requires - // the framework to be running. - removePackageUpdates(packageName, device); - - if (!isPackageInstalled(packageName, device)) { - CLog.i("Package %s has been removed.", packageName); - return; - } - - String packageInstallDirectory = getPackageInstallDirectory(packageName, device); - CLog.d("Install directory for package %s is %s", packageName, packageInstallDirectory); - - if (!isPackagePathSystemApp(packageInstallDirectory)) { - CLog.w("%s is not a system app, skipping", packageName); - return; - } - - CLog.i("Uninstalling system app %s", packageName); - - runWithWritableFilesystem( - device, - () -> - runWithFrameworkOff( - device, - () -> { - removePackageInstallDirectory(packageInstallDirectory, device); - removePackageData(packageName, device); - })); - } - - private interface PreparerTask { - void run() throws TargetSetupError, DeviceNotAvailableException; - } - - private static void runWithFrameworkOff(ITestDevice device, PreparerTask action) - throws TargetSetupError, DeviceNotAvailableException { - stopFramework(device); - - try { - action.run(); - } finally { - startFramework(device); - } - } - - private static void runWithWritableFilesystem(ITestDevice device, PreparerTask action) - throws TargetSetupError, DeviceNotAvailableException { - runAsRoot( - device, - () -> { - // TODO(yuexima): The remountSystemWritable method may internally disable dm - // verity on some devices. Consider restoring verity which would require a - // reboot. - device.remountSystemWritable(); - - try { - action.run(); - } finally { - remountSystemReadOnly(device); - } - }); - } - - private static void runAsRoot(ITestDevice device, PreparerTask action) - throws TargetSetupError, DeviceNotAvailableException { - boolean disableRootAfterUninstall = false; - - if (!device.isAdbRoot()) { - if (!device.enableAdbRoot()) { - throw new TargetSetupError("Failed to enable adb root"); - } - - disableRootAfterUninstall = true; - } - - try { - action.run(); - } finally { - if (disableRootAfterUninstall && !device.disableAdbRoot()) { - throw new TargetSetupError("Failed to disable adb root"); - } - } - } - - private static void stopFramework(ITestDevice device) - throws TargetSetupError, DeviceNotAvailableException { - // 'stop' is a blocking command. - executeShellCommandOrThrow(device, "stop", "Failed to stop framework"); - // Set the boot complete flags to false. When the framework is started again, both flags - // will be set to true by the system upon the completion of restarting. This allows - // ITestDevice#waitForBootComplete to wait for framework start, and it only works - // when adb is rooted. - device.setProperty(SYSPROP_SYS_BOOT_COMPLETED, "0"); - device.setProperty(SYSPROP_DEV_BOOTCOMPLETE, "0"); - } - - private static void startFramework(ITestDevice device) - throws TargetSetupError, DeviceNotAvailableException { - // 'start' is a non-blocking command. - executeShellCommandOrThrow(device, "start", "Failed to start framework"); - // This wait only blocks if the boot completed flags are set to 0. - device.waitForBootComplete(WAIT_FOR_BOOT_COMPLETE_TIMEOUT_MILLIS); - } - - private static CommandResult executeShellCommandOrThrow( - ITestDevice device, String command, String failureMessage) - throws TargetSetupError, DeviceNotAvailableException { - CommandResult commandResult = device.executeShellV2Command(command); - - if (commandResult.getStatus() != CommandStatus.SUCCESS) { - throw new TargetSetupError( - String.format("%s; Command result: %s", failureMessage, commandResult)); - } - - return commandResult; - } - - private static CommandResult executeShellCommandOrLog( - ITestDevice device, String command, String failureMessage) - throws DeviceNotAvailableException { - CommandResult commandResult = device.executeShellV2Command(command); - if (commandResult.getStatus() != CommandStatus.SUCCESS) { - CLog.e("%s. Command result: %s", failureMessage, commandResult); - } - - return commandResult; - } - - private static void remountSystemReadOnly(ITestDevice device) - throws TargetSetupError, DeviceNotAvailableException { - executeShellCommandOrThrow( - device, - "mount -o ro,remount /system", - "Failed to remount system partition as read only"); - } - - private static boolean isPackagePathSystemApp(String packagePath) { - return packagePath.startsWith("/system/") || packagePath.startsWith("/product/"); - } - - private static void removePackageInstallDirectory( - String packageInstallDirectory, ITestDevice device) - throws TargetSetupError, DeviceNotAvailableException { - CLog.i("Removing package install directory %s", packageInstallDirectory); - executeShellCommandOrThrow( - device, - String.format("rm -r %s", packageInstallDirectory), - String.format( - "Failed to remove system app package path %s", packageInstallDirectory)); - } - - private static void removePackageUpdates(String packageName, ITestDevice device) - throws TargetSetupError, DeviceNotAvailableException { - CLog.i("Removing package updates for %s", packageName); - - // A system package may have update packages. If so, each `adb uninstall` call - // only uninstalls the latest update. To remove all update packages we can - // call uninstall repeatedly until the command fails. - for (int i = 0; i < MAX_NUMBER_OF_UPDATES; i++) { - String errMsg = device.uninstallPackage(packageName); - if (errMsg != null) { - CLog.d("Completed removing updates as the uninstall command returned: %s", errMsg); - return; - } - CLog.i("Removed an update package for %s", packageName); - } - - throw new TargetSetupError("Too many updates were uninstalled. Something must be wrong."); - } - - private static void removePackageData(String packageName, ITestDevice device) - throws DeviceNotAvailableException { - String dataPath = String.format("/data/data/%s", packageName); - CLog.i("Removing package data directory for %s", dataPath); - executeShellCommandOrLog( - device, - String.format("rm -r %s", dataPath), - String.format( - "Failed to remove system app data %s from %s", packageName, dataPath)); - } - - private static boolean isPackageManagerRunning(ITestDevice device) - throws DeviceNotAvailableException { - return device.executeShellV2Command(PM_CHECK_COMMAND).getStatus() == CommandStatus.SUCCESS; - } - - private static boolean isPackageInstalled(String packageName, ITestDevice device) - throws TargetSetupError, DeviceNotAvailableException { - CommandResult commandResult = - executeShellCommandOrThrow( - device, - String.format("pm list packages %s", packageName), - "Failed to execute pm command"); - - if (commandResult.getStdout() == null) { - throw new TargetSetupError( - String.format( - "Failed to get pm command output: %s", commandResult.getStdout())); - } - - return Arrays.asList(commandResult.getStdout().split("\\r?\\n")) - .contains(String.format("package:%s", packageName)); - } - - private static String getPackageInstallDirectory(String packageName, ITestDevice device) - throws TargetSetupError, DeviceNotAvailableException { - CommandResult commandResult = - executeShellCommandOrThrow( - device, - String.format("pm path %s", packageName), - "Failed to execute pm command"); - - if (commandResult.getStdout() == null - || !commandResult.getStdout().startsWith("package:")) { - throw new TargetSetupError( - String.format( - "Failed to get pm path command output %s", commandResult.getStdout())); - } - - String packageInstallPath = commandResult.getStdout().substring("package:".length()); - return Paths.get(packageInstallPath).getParent().toString(); - } -} diff --git a/harness/src/main/resources/META-INF/services/com.android.tradefed.config.remote.IRemoteFileResolver b/harness/src/main/resources/META-INF/services/com.android.tradefed.config.remote.IRemoteFileResolver deleted file mode 100644 index be67f9b..0000000 --- a/harness/src/main/resources/META-INF/services/com.android.tradefed.config.remote.IRemoteFileResolver +++ /dev/null @@ -1 +0,0 @@ -com.android.csuite.config.AppRemoteFileResolver diff --git a/harness/src/main/resources/config/csuite-base.xml b/harness/src/main/resources/config/csuite-base.xml index 03e418b..6431154 100644 --- a/harness/src/main/resources/config/csuite-base.xml +++ b/harness/src/main/resources/config/csuite-base.xml @@ -28,12 +28,5 @@ <result_reporter class="com.android.compatibility.common.tradefed.result.suite.CompatibilityProtoResultReporter" /> <result_reporter class="com.android.tradefed.result.suite.SuiteResultReporter" /> - <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer"> - <option name="test-file-name" value="csuite-launch-instrumentation.apk"/> - </target_preparer> - <!-- Cleans generated module files after test --> - <target_preparer class="com.android.csuite.config.ModuleGenerator" /> - - <object type="PACKAGE_NAME_PROVIDER" class="com.android.csuite.core.CommandLinePackageNameProvider" /> - <object type="PACKAGE_NAME_PROVIDER" class="com.android.csuite.core.FileBasedPackageNameProvider" /> + <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparerConfigurationReceiver" /> </configuration> diff --git a/integration_tests/csuite_test_template.xml b/harness/src/main/resources/config/launch.xml index 837716a..a1a158d 100644 --- a/integration_tests/csuite_test_template.xml +++ b/harness/src/main/resources/config/launch.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project +<!-- 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. @@ -13,11 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -<configuration> - <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest"> - <option name="par-file-name" value="{MODULE}"/> - <option name="inject-serial-option" value="true"/> - <option name="use-test-output-file" value="true"/> - <option name="test-timeout" value="10m"/> - </test> +<configuration description="C-Suite Compatibility Launch Test Plan"> + <include name="csuite-base" /> + + <option name="plan" value="launch" /> + + <option name="compatibility:module-metadata-include-filter" key="plan" value="app-launch" /> </configuration> diff --git a/harness/src/test/java/com/android/compatibility/AppCompatibilityTestTest.java b/harness/src/test/java/com/android/compatibility/AppCompatibilityTestTest.java new file mode 100644 index 0000000..18f3368 --- /dev/null +++ b/harness/src/test/java/com/android/compatibility/AppCompatibilityTestTest.java @@ -0,0 +1,334 @@ +/* + * 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.compatibility; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.tradefed.testtype.InstrumentationTest; +import com.android.tradefed.util.PublicApkUtil.ApkInfo; + + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +@RunWith(JUnit4.class) +public final class AppCompatibilityTestTest { + + private ConcreteAppCompatibilityTest mSut; + + private class ConcreteAppCompatibilityTest extends AppCompatibilityTest { + + ConcreteAppCompatibilityTest() { + super(null, null, null); + } + + @Override + protected InstrumentationTest createInstrumentationTest(String packageBeingTested) { + return null; + } + } + + @Before + public void setUp() { + mSut = new ConcreteAppCompatibilityTest(); + } + + @Test(expected = IllegalArgumentException.class) + public void addIncludeFilter_nullIncludeFilter_throwsException() { + mSut.addIncludeFilter(null); + } + + @Test(expected = IllegalArgumentException.class) + public void addIncludeFilter_emptyIncludeFilter_throwsException() { + mSut.addIncludeFilter(""); + } + + @Test + public void addIncludeFilter_validIncludeFilter() { + mSut.addIncludeFilter("test_filter"); + + assertTrue(mSut.mIncludeFilters.contains("test_filter")); + } + + @Test(expected = NullPointerException.class) + public void addAllIncludeFilters_nullIncludeFilter_throwsException() { + mSut.addAllIncludeFilters(null); + } + + @Test + public void addAllIncludeFilters_validIncludeFilters() { + Set<String> test_filters = new TreeSet<>(); + test_filters.add("filter_one"); + test_filters.add("filter_two"); + + mSut.addAllIncludeFilters(test_filters); + + assertTrue(mSut.mIncludeFilters.contains("filter_one")); + assertTrue(mSut.mIncludeFilters.contains("filter_two")); + } + + @Test + public void clearIncludeFilters() { + mSut.addIncludeFilter("filter_test"); + + mSut.clearIncludeFilters(); + + assertTrue(mSut.mIncludeFilters.isEmpty()); + } + + @Test + public void getIncludeFilters() { + mSut.addIncludeFilter("filter_test"); + + assertEquals(mSut.mIncludeFilters, mSut.getIncludeFilters()); + } + + @Test(expected = IllegalArgumentException.class) + public void addExcludeFilter_nullExcludeFilter_throwsException() { + mSut.addExcludeFilter(null); + } + + @Test(expected = IllegalArgumentException.class) + public void addExcludeFilter_emptyExcludeFilter_throwsException() { + mSut.addExcludeFilter(""); + } + + @Test + public void addExcludeFilter_validExcludeFilter() { + mSut.addExcludeFilter("test_filter"); + + assertTrue(mSut.mExcludeFilters.contains("test_filter")); + } + + @Test(expected = NullPointerException.class) + public void addAllExcludeFilters_nullExcludeFilters_throwsException() { + mSut.addAllExcludeFilters(null); + } + + @Test + public void addAllExcludeFilters_validExcludeFilters() { + Set<String> test_filters = new TreeSet<>(); + test_filters.add("filter_one"); + test_filters.add("filter_two"); + + mSut.addAllExcludeFilters(test_filters); + + assertTrue(mSut.mExcludeFilters.contains("filter_one")); + assertTrue(mSut.mExcludeFilters.contains("filter_two")); + } + + @Test + public void clearExcludeFilters() { + mSut.addExcludeFilter("filter_test"); + + mSut.clearExcludeFilters(); + + assertTrue(mSut.mExcludeFilters.isEmpty()); + } + + @Test + public void getExcludeFilters() { + mSut.addExcludeFilter("filter_test"); + + assertEquals(mSut.mExcludeFilters, mSut.getExcludeFilters()); + } + + @Test + public void filterApk_withNoFilter() { + List<ApkInfo> testList = createApkList(); + + List<ApkInfo> filteredList = mSut.filterApk(testList); + + assertEquals(filteredList, testList); + } + + @Test + public void filterApk_withRelatedIncludeFilters() { + List<ApkInfo> testList = createApkList(); + mSut.addIncludeFilter("filter_one"); + + List<ApkInfo> filteredList = mSut.filterApk(testList); + + assertEquals(convertList(filteredList), Arrays.asList("filter_one")); + } + + @Test + public void filterApk_withUnrelatedIncludeFilters() { + List<ApkInfo> testList = createApkList(); + mSut.addIncludeFilter("filter_three"); + + List<ApkInfo> filteredList = mSut.filterApk(testList); + + assertTrue(filteredList.isEmpty()); + } + + @Test + public void filterApk_withRelatedExcludeFilters() { + List<ApkInfo> testList = createApkList(); + mSut.addExcludeFilter("filter_one"); + + List<ApkInfo> filteredList = mSut.filterApk(testList); + + assertEquals(convertList(filteredList), Arrays.asList("filter_two")); + } + + @Test + public void filterApk_withUnrelatedExcludeFilters() { + List<ApkInfo> testList = createApkList(); + mSut.addExcludeFilter("filter_three"); + + List<ApkInfo> filteredList = mSut.filterApk(testList); + + assertEquals(filteredList, testList); + } + + @Test + public void filterApk_withSameIncludeAndExcludeFilters() { + List<ApkInfo> testList = createApkList(); + mSut.addIncludeFilter("filter_one"); + mSut.addExcludeFilter("filter_one"); + + List<ApkInfo> filteredList = mSut.filterApk(testList); + + assertTrue(filteredList.isEmpty()); + } + + @Test + public void filterApk_withDifferentIncludeAndExcludeFilter() { + List<ApkInfo> testList = createApkList(); + mSut.addIncludeFilter("filter_one"); + mSut.addIncludeFilter("filter_two"); + mSut.addExcludeFilter("filter_two"); + + List<ApkInfo> filteredList = mSut.filterApk(testList); + + assertEquals(convertList(filteredList), Arrays.asList("filter_one")); + } + + @Test + public void filterApk_withUnrelatedIncludeFilterAndRelatedExcludeFilter() { + List<ApkInfo> testList = createApkList(); + mSut.addIncludeFilter("filter_three"); + mSut.addExcludeFilter("filter_two"); + + List<ApkInfo> filteredList = mSut.filterApk(testList); + + assertTrue(filteredList.isEmpty()); + } + + @Test + public void filterApk_withRelatedIncludeFilterAndUnrelatedExcludeFilter() { + List<ApkInfo> testList = createApkList(); + mSut.addIncludeFilter("filter_one"); + mSut.addExcludeFilter("filter_three"); + + List<ApkInfo> filteredList = mSut.filterApk(testList); + + assertEquals(convertList(filteredList), Arrays.asList("filter_one")); + } + + private List<ApkInfo> createApkList() { + List<ApkInfo> testList = new ArrayList<>(); + ApkInfo apk_info_one = new ApkInfo(0, "filter_one", "", "", ""); + ApkInfo apk_info_two = new ApkInfo(0, "filter_two", "", "", ""); + testList.add(apk_info_one); + testList.add(apk_info_two); + return testList; + } + + private List<String> convertList(List<ApkInfo> apkList) { + List<String> convertedList = new ArrayList<>(); + for (ApkInfo apkInfo : apkList) { + convertedList.add(apkInfo.packageName); + } + return convertedList; + } + + @Test + public void filterTest_withEmptyFilter() { + assertTrue(mSut.filterTest("filter_one")); + } + + @Test + public void filterTest_withRelatedIncludeFilter() { + mSut.addIncludeFilter("filter_one"); + + assertTrue(mSut.filterTest("filter_one")); + } + + @Test + public void filterTest_withUnrelatedIncludeFilter() { + mSut.addIncludeFilter("filter_two"); + + assertFalse(mSut.filterTest("filter_one")); + } + + @Test + public void filterTest_withRelatedExcludeFilter() { + mSut.addExcludeFilter("filter_one"); + + assertFalse(mSut.filterTest("filter_one")); + } + + @Test + public void filterTest_withUnrelatedExcludeFilter() { + mSut.addExcludeFilter("filter_two"); + + assertTrue(mSut.filterTest("filter_one")); + } + + @Test + public void filterTest_withSameIncludeAndExcludeFilters() { + mSut.addIncludeFilter("filter_one"); + mSut.addExcludeFilter("filter_one"); + + assertFalse(mSut.filterTest("filter_one")); + } + + @Test + public void filterTest_withUnrelatedIncludeFilterAndRelatedExcludeFilter() { + mSut.addIncludeFilter("filter_one"); + mSut.addExcludeFilter("filter_two"); + + assertFalse(mSut.filterTest("filter_two")); + } + + @Test + public void filterTest_withRelatedIncludeFilterAndUnrelatedExcludeFilter() { + mSut.addIncludeFilter("filter_one"); + mSut.addExcludeFilter("filter_two"); + + assertTrue(mSut.filterTest("filter_one")); + } + + @Test + public void filterTest_withUnRelatedIncludeFilterAndUnrelatedExcludeFilter() { + mSut.addIncludeFilter("filter_one"); + mSut.addExcludeFilter("filter_two"); + + assertFalse(mSut.filterTest("filter_three")); + } +} diff --git a/harness/src/test/java/com/android/csuite/CSuiteUnitTests.java b/harness/src/test/java/com/android/compatibility/CSuiteUnitTests.java index 6eb1103..b87402e 100644 --- a/harness/src/test/java/com/android/csuite/CSuiteUnitTests.java +++ b/harness/src/test/java/com/android/compatibility/CSuiteUnitTests.java @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.csuite; +package com.android.compatibility; + +import com.android.compatibility.targetprep.AppSetupPreparerConfigurationReceiverTest; +import com.android.compatibility.targetprep.AppSetupPreparerTest; +import com.android.compatibility.testtype.AppLaunchTestTest; import org.junit.runner.RunWith; import org.junit.runners.Suite; @@ -21,16 +25,10 @@ import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) @SuiteClasses({ - com.android.compatibility.targetprep.AppSetupPreparerTest.class, - com.android.compatibility.targetprep.CheckGmsPreparerTest.class, - com.android.compatibility.testtype.AppLaunchTestTest.class, - com.android.csuite.config.AppRemoteFileResolverTest.class, - com.android.csuite.config.ModuleGeneratorTest.class, - com.android.csuite.core.CommandLinePackageNameProviderTest.class, - com.android.csuite.core.FileBasedPackageNameProviderTest.class, - com.android.csuite.core.SystemAppUninstallerTest.class, - com.android.csuite.testing.CorrespondencesTest.class, - com.android.csuite.testing.MoreAssertsTest.class, + AppCompatibilityTestTest.class, + AppLaunchTestTest.class, + AppSetupPreparerTest.class, + AppSetupPreparerConfigurationReceiverTest.class, }) public final class CSuiteUnitTests { // Intentionally empty. diff --git a/harness/src/test/java/com/android/compatibility/targetprep/AppSetupPreparerConfigurationReceiverTest.java b/harness/src/test/java/com/android/compatibility/targetprep/AppSetupPreparerConfigurationReceiverTest.java new file mode 100644 index 0000000..e708a67 --- /dev/null +++ b/harness/src/test/java/com/android/compatibility/targetprep/AppSetupPreparerConfigurationReceiverTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.compatibility.targetprep; + +import com.android.tradefed.build.BuildInfo; +import com.android.tradefed.build.IBuildInfo; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; + +@RunWith(JUnit4.class) +public final class AppSetupPreparerConfigurationReceiverTest { + + @Test + public void setUp_noneNullGcsApkDirOption_putsInBuildInfo() { + File optionGcsApkDir = new File("dir"); + AppSetupPreparerConfigurationReceiver preparer = + new AppSetupPreparerConfigurationReceiver(optionGcsApkDir); + IBuildInfo buildInfo = new BuildInfo(); + + preparer.setUp(null, buildInfo); + + assertThat(buildInfo.getBuildAttributes()) + .containsEntry(AppSetupPreparer.OPTION_GCS_APK_DIR, optionGcsApkDir.getPath()); + } + + @Test + public void setUp_nullGcsApkDirOption_doesNotPutInBuildInfo() { + File optionGcsApkDir = null; + AppSetupPreparerConfigurationReceiver preparer = + new AppSetupPreparerConfigurationReceiver(optionGcsApkDir); + IBuildInfo buildInfo = new BuildInfo(); + + preparer.setUp(null, buildInfo); + + assertThat(buildInfo.getBuildAttributes()) + .doesNotContainKey(AppSetupPreparer.OPTION_GCS_APK_DIR); + } +} diff --git a/harness/src/test/java/com/android/compatibility/targetprep/AppSetupPreparerTest.java b/harness/src/test/java/com/android/compatibility/targetprep/AppSetupPreparerTest.java index e2e4352..7614f05 100644 --- a/harness/src/test/java/com/android/compatibility/targetprep/AppSetupPreparerTest.java +++ b/harness/src/test/java/com/android/compatibility/targetprep/AppSetupPreparerTest.java @@ -15,492 +15,125 @@ */ package com.android.compatibility.targetprep; +import com.android.tradefed.build.BuildInfo; import com.android.tradefed.build.IBuildInfo; -import com.android.tradefed.config.ArgsOptionParser; -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.invoker.TestInformation; import com.android.tradefed.targetprep.TargetSetupError; import com.android.tradefed.targetprep.TestAppInstallSetup; -import com.android.tradefed.util.AaptParser.AaptVersion; - -import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ListMultimap; - import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.mockito.ArgumentCaptor; -import org.mockito.internal.stubbing.answers.AnswersWithDelay; -import org.mockito.stubbing.Answer; 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.time.Duration; -import java.util.ArrayList; -import java.util.Map; @RunWith(JUnit4.class) -public final class AppSetupPreparerTest { - private static final ITestDevice NULL_DEVICE = null; - private static final IBuildInfo NULL_BUILD_INFO = null; - private static final String TEST_PACKAGE_NAME = "test.package.name"; - private static final Answer<Object> EMPTY_ANSWER = (i) -> null; - - @Rule public final TemporaryFolder tempFolder = new TemporaryFolder(); - - @Test - public void setUp_unresolvedAppUri_installs() throws Exception { - String appUri = "app://com.example.app"; - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_TEST_FILE_NAME, appUri) - .build(); - - preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO); - - verify(installer).addTestFile(new File(appUri)); - } - - @Test - public void tearDown_forwardsToInstaller() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - AppSetupPreparer preparer = new PreparerBuilder().setInstaller(installer).build(); - TestInformation testInfo = TestInformation.newBuilder().build(); - - preparer.tearDown(testInfo, null); - - verify(installer).tearDown(testInfo, null); - } - - @Test - public void setUp_withinRetryLimit_doesNotThrowException() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - doThrow(new TargetSetupError("Still failing")) - .doNothing() - .when(installer) - .setUp(any(), any()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_MAX_RETRY, "1") - .build(); - - preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO); - } - - @Test - public void setUp_exceedsRetryLimit_throwsException() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - doThrow(new TargetSetupError("Still failing")) - .doThrow(new TargetSetupError("Still failing")) - .doNothing() - .when(installer) - .setUp(any(), any()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_MAX_RETRY, "1") - .build(); - - assertThrows(TargetSetupError.class, () -> preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO)); - } - - @Test - public void setUp_negativeTimeout_throwsException() throws Exception { - AppSetupPreparer preparer = - new PreparerBuilder() - .setOption(AppSetupPreparer.OPTION_SETUP_TIMEOUT_MILLIS, "-1") - .build(); - - assertThrows( - IllegalArgumentException.class, () -> preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO)); - } - - @Test - public void setUp_withinTimeout_doesNotThrowException() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - doAnswer(new AnswersWithDelay(10, EMPTY_ANSWER)).when(installer).setUp(any(), any()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_SETUP_TIMEOUT_MILLIS, "1000") - .build(); - - preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO); - } - - @Test - public void setUp_exceedsTimeout_throwsException() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - doAnswer(new AnswersWithDelay(10, EMPTY_ANSWER)).when(installer).setUp(any(), any()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_SETUP_TIMEOUT_MILLIS, "5") - .build(); - - assertThrows(TargetSetupError.class, () -> preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO)); - } - - @Test - public void setUp_timesOutWithoutExceedingRetryLimit_doesNotThrowException() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - doAnswer(new AnswersWithDelay(10, EMPTY_ANSWER)) - .doNothing() - .when(installer) - .setUp(any(), any()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_MAX_RETRY, "1") - .setOption(AppSetupPreparer.OPTION_SETUP_TIMEOUT_MILLIS, "5") - .build(); - - preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO); - } - - @Test - public void setUp_timesOutAndExceedsRetryLimit_doesNotThrowException() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - doAnswer(new AnswersWithDelay(10, EMPTY_ANSWER)).when(installer).setUp(any(), any()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_MAX_RETRY, "1") - .setOption(AppSetupPreparer.OPTION_SETUP_TIMEOUT_MILLIS, "5") - .build(); - - assertThrows(TargetSetupError.class, () -> preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO)); - } - - @Test - public void setUp_zeroMaxRetry_runsOnce() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - doNothing().when(installer).setUp(any(), any()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_MAX_RETRY, "0") - .build(); - - preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO); - - verify(installer).setUp(any(), any()); - } +public class AppSetupPreparerTest { - @Test - public void setUp_positiveMaxRetryButNoException_runsOnlyOnce() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - doNothing().when(installer).setUp(any(), any()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_MAX_RETRY, "1") - .build(); + private static final String OPTION_GCS_APK_DIR = "gcs-apk-dir"; + public static final ITestDevice NULL_DEVICE = null; - preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO); + @Rule public final TemporaryFolder tempFolder = new TemporaryFolder(); - verify(installer).setUp(any(), any()); - } + private final IBuildInfo mBuildInfo = new BuildInfo(); + private final TestAppInstallSetup mMockAppInstallSetup = mock(TestAppInstallSetup.class); + private final AppSetupPreparer mPreparer = + new AppSetupPreparer("package_name", mMockAppInstallSetup); @Test - public void setUp_negativeMaxRetry_throwsException() throws Exception { - AppSetupPreparer preparer = - new PreparerBuilder().setOption(AppSetupPreparer.OPTION_MAX_RETRY, "-1").build(); + public void setUp_gcsApkDirIsNull_throwsException() + throws DeviceNotAvailableException, TargetSetupError { + mBuildInfo.addBuildAttribute(OPTION_GCS_APK_DIR, null); - assertThrows( - IllegalArgumentException.class, () -> preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO)); + assertThrows(NullPointerException.class, () -> mPreparer.setUp(NULL_DEVICE, mBuildInfo)); } @Test - public void setUp_deviceNotAvailableAndWaitEnabled_throwsDeviceNotAvailableException() - throws Exception { - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller( - mockInstallerThatThrows( - new TargetSetupError("Connection reset by peer."))) - .setOption(AppSetupPreparer.OPTION_WAIT_FOR_DEVICE_AVAILABLE_SECONDS, "1") - .build(); - ITestDevice device = createUnavailableDevice(); + public void setUp_gcsApkDirIsNotDir_throwsException() + throws IOException, DeviceNotAvailableException, TargetSetupError { + File tempFile = tempFolder.newFile("temp_file_name"); + mBuildInfo.addBuildAttribute(OPTION_GCS_APK_DIR, tempFile.getPath()); assertThrows( - DeviceNotAvailableException.class, () -> preparer.setUp(device, NULL_BUILD_INFO)); + IllegalArgumentException.class, () -> mPreparer.setUp(NULL_DEVICE, mBuildInfo)); } @Test - public void setUp_deviceAvailableAndWaitEnabled_doesNotChangeException() throws Exception { - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller( - mockInstallerThatThrows( - new TargetSetupError("Connection reset by peer."))) - .setOption(AppSetupPreparer.OPTION_WAIT_FOR_DEVICE_AVAILABLE_SECONDS, "1") - .build(); - ITestDevice device = createAvailableDevice(); - - assertThrows(TargetSetupError.class, () -> preparer.setUp(device, NULL_BUILD_INFO)); - } - - @Test - public void setUp_deviceNotAvailableAndWaitDisabled_doesNotChangeException() throws Exception { - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller( - mockInstallerThatThrows( - new TargetSetupError("Connection reset by peer."))) - .setOption(AppSetupPreparer.OPTION_WAIT_FOR_DEVICE_AVAILABLE_SECONDS, "-1") - .build(); - ITestDevice device = createUnavailableDevice(); - - assertThrows(TargetSetupError.class, () -> preparer.setUp(device, NULL_BUILD_INFO)); - } - - @Test - public void setUp_negativeExponentialBackoffMultiplier_throwsIllegalArgumentException() - throws Exception { - AppSetupPreparer preparer = - new PreparerBuilder() - .setOption( - AppSetupPreparer.OPTION_EXPONENTIAL_BACKOFF_MULTIPLIER_SECONDS, - "-1") - .build(); + public void setUp_packageDirDoesNotExist_throwsError() + throws IOException, DeviceNotAvailableException, TargetSetupError { + File gcsApkDir = tempFolder.newFolder("gcs_apk_dir"); + mBuildInfo.addBuildAttribute(OPTION_GCS_APK_DIR, gcsApkDir.getPath()); assertThrows( - IllegalArgumentException.class, () -> preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO)); + IllegalArgumentException.class, () -> mPreparer.setUp(NULL_DEVICE, mBuildInfo)); } @Test - public void setUp_testFileNameOptionSet_forwardsToInstaller() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - ArgumentCaptor<File> captor = ArgumentCaptor.forClass(File.class); - doNothing().when(installer).addTestFile(captor.capture()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_TEST_FILE_NAME, "additional1.apk") - .setOption(AppSetupPreparer.OPTION_TEST_FILE_NAME, "additional2.apk") - .build(); - - preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO); + public void setUp_apkDoesNotExist() throws Exception { + File gcsApkDir = tempFolder.newFolder("gcs_apk_dir"); + createPackageFile(gcsApkDir, "package_name", "non_apk_file"); + mBuildInfo.addBuildAttribute(OPTION_GCS_APK_DIR, gcsApkDir.getPath()); - assertThat(captor.getAllValues()) - .containsAtLeast(new File("additional1.apk"), new File("additional2.apk")); + assertThrows(TargetSetupError.class, () -> mPreparer.setUp(NULL_DEVICE, mBuildInfo)); } @Test - public void setUp_installArgOptionSet_forwardsToInstaller() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); - doNothing().when(installer).addInstallArg(captor.capture()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_INSTALL_ARG, "-arg1") - .setOption(AppSetupPreparer.OPTION_INSTALL_ARG, "-arg2") - .build(); + public void setUp_installSplitApk() throws Exception { + File gcsApkDir = tempFolder.newFolder("gcs_apk_dir"); + File packageDir = new File(gcsApkDir.getPath(), "package_name"); + createPackageFile(gcsApkDir, "package_name", "apk_name_1.apk"); + createPackageFile(gcsApkDir, "package_name", "apk_name_2.apk"); + mBuildInfo.addBuildAttribute(OPTION_GCS_APK_DIR, gcsApkDir.getPath()); - preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO); + mPreparer.setUp(NULL_DEVICE, mBuildInfo); - assertThat(captor.getAllValues()).containsExactly("-arg1", "-arg2"); + verify(mMockAppInstallSetup).setAltDir(packageDir); + verify(mMockAppInstallSetup).addSplitApkFileNames("apk_name_2.apk,apk_name_1.apk"); + verify(mMockAppInstallSetup).setUp(any(), any()); } @Test - public void setUp_installIncrementalOptionSet_forwardsToInstaller() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); + public void setUp_installNonSplitApk() throws Exception { + File gcsApkDir = tempFolder.newFolder("gcs_apk_dir"); + File packageDir = new File(gcsApkDir.getPath(), "package_name"); + createPackageFile(gcsApkDir, "package_name", "apk_name_1.apk"); + mBuildInfo.addBuildAttribute(OPTION_GCS_APK_DIR, gcsApkDir.getPath()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_INCREMENTAL_INSTALL, "true") - .build(); + mPreparer.setUp(NULL_DEVICE, mBuildInfo); - preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO); - String result = ArgsOptionParser.getOptionHelp(false, installer); - System.out.println(result); - - assertThat(result).contains("incremental"); - } - - @Test - public void setUp_aaptVersionOptionSet_forwardsToInstaller() throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - ArgumentCaptor<AaptVersion> captor = ArgumentCaptor.forClass(AaptVersion.class); - doNothing().when(installer).setAaptVersion(captor.capture()); - AppSetupPreparer preparer = - new PreparerBuilder() - .setInstaller(installer) - .setOption(AppSetupPreparer.OPTION_AAPT_VERSION, "AAPT2") - .build(); - - preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO); - - assertThat(captor.getValue()).isEqualTo(AaptVersion.AAPT2); + verify(mMockAppInstallSetup).setAltDir(packageDir); + verify(mMockAppInstallSetup).addTestFileName("apk_name_1.apk"); + verify(mMockAppInstallSetup).setUp(any(), any()); } @Test - public void setUp_zeroExponentialBackoffMultiplier_noSleepBetweenRetries() throws Exception { - FakeSleeper fakeSleeper = new FakeSleeper(); - AppSetupPreparer preparer = - new PreparerBuilder() - .setSleeper(fakeSleeper) - .setInstaller(mockInstallerThatThrows(new TargetSetupError("Oops"))) - .setOption( - AppSetupPreparer.OPTION_EXPONENTIAL_BACKOFF_MULTIPLIER_SECONDS, "0") - .setOption(AppSetupPreparer.OPTION_MAX_RETRY, "1") - .build(); - - assertThrows(TargetSetupError.class, () -> preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO)); - assertThat(fakeSleeper.getSleepHistory().get(0)).isEqualTo(Duration.ofSeconds(0)); - } - - @Test - public void setUp_positiveExponentialBackoffMultiplier_sleepsBetweenRetries() throws Exception { - FakeSleeper fakeSleeper = new FakeSleeper(); - AppSetupPreparer preparer = - new PreparerBuilder() - .setSleeper(fakeSleeper) - .setInstaller(mockInstallerThatThrows(new TargetSetupError("Oops"))) - .setOption( - AppSetupPreparer.OPTION_EXPONENTIAL_BACKOFF_MULTIPLIER_SECONDS, "3") - .setOption(AppSetupPreparer.OPTION_MAX_RETRY, "3") - .build(); - - assertThrows(TargetSetupError.class, () -> preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO)); - assertThat(fakeSleeper.getSleepHistory().get(0)).isEqualTo(Duration.ofSeconds(3)); - assertThat(fakeSleeper.getSleepHistory().get(1)).isEqualTo(Duration.ofSeconds(9)); - assertThat(fakeSleeper.getSleepHistory().get(2)).isEqualTo(Duration.ofSeconds(27)); - } - - @Test - public void setUp_interruptedDuringBackoff_throwsException() throws Exception { - FakeSleeper fakeSleeper = new FakeInterruptedSleeper(); - AppSetupPreparer preparer = - new PreparerBuilder() - .setSleeper(fakeSleeper) - .setInstaller(mockInstallerThatThrows(new TargetSetupError("Oops"))) - .setOption( - AppSetupPreparer.OPTION_EXPONENTIAL_BACKOFF_MULTIPLIER_SECONDS, "3") - .setOption(AppSetupPreparer.OPTION_MAX_RETRY, "3") - .build(); - - try { - assertThrows( - TargetSetupError.class, () -> preparer.setUp(NULL_DEVICE, NULL_BUILD_INFO)); - assertThat(Thread.currentThread().isInterrupted()).isTrue(); - assertThat(fakeSleeper.getSleepHistory().size()).isEqualTo(1); - } finally { - // Clear interrupt to not interfere with future tests. - Thread.interrupted(); - } - } - - private TestAppInstallSetup mockInstallerThatThrows(Exception e) throws Exception { - TestAppInstallSetup installer = mock(TestAppInstallSetup.class); - doThrow(e).when(installer).setUp(any(), any()); - return installer; - } - - private File createPackageFile(String packageName, String apkName) throws IOException { - Path packageDir = - Files.createDirectories( - Paths.get(tempFolder.newFolder("any").getAbsolutePath(), packageName)); - Files.createFile(packageDir.resolve(apkName)); - - return packageDir.toFile(); - } - - private static ITestDevice createUnavailableDevice() throws Exception { - ITestDevice device = mock(ITestDevice.class); - when(device.getProperty(any())).thenReturn(null); - doThrow(new DeviceNotAvailableException("_", "serial")) - .when(device) - .waitForDeviceAvailable(anyLong()); - return device; - } - - private static ITestDevice createAvailableDevice() throws Exception { - ITestDevice device = mock(ITestDevice.class); - when(device.getProperty(any())).thenReturn(""); - when(device.waitForDeviceShell(anyLong())).thenReturn(true); - doNothing().when(device).waitForDeviceAvailable(anyLong()); - - return device; - } - - private static class FakeSleeper implements AppSetupPreparer.Sleeper { - private ArrayList<Duration> mSleepHistory = new ArrayList<>(); - - @Override - public void sleep(Duration duration) throws InterruptedException { - mSleepHistory.add(duration); - } + public void tearDown() throws Exception { + TestInformation testInfo = TestInformation.newBuilder().build(); - ArrayList<Duration> getSleepHistory() { - return mSleepHistory; - } - } + mPreparer.tearDown(testInfo, null); - private static class FakeInterruptedSleeper extends FakeSleeper { - @Override - public void sleep(Duration duration) throws InterruptedException { - super.sleep(duration); - throw new InterruptedException("_"); - } + verify(mMockAppInstallSetup, times(1)).tearDown(testInfo, null); } - private static final class PreparerBuilder { - - private AppSetupPreparer.Sleeper mSleeper = new FakeSleeper(); - private TestAppInstallSetup mInstaller = mock(TestAppInstallSetup.class); - private final ListMultimap<String, String> mOptions = ArrayListMultimap.create(); - - PreparerBuilder setSleeper(AppSetupPreparer.Sleeper sleeper) { - this.mSleeper = sleeper; - return this; - } - - PreparerBuilder setInstaller(TestAppInstallSetup installer) { - this.mInstaller = installer; - return this; - } - - PreparerBuilder setOption(String key, String value) { - mOptions.put(key, value); - return this; - } - - AppSetupPreparer build() throws ConfigurationException { - AppSetupPreparer preparer = new AppSetupPreparer(mInstaller, mSleeper); - OptionSetter optionSetter = new OptionSetter(preparer); - - for (Map.Entry<String, String> e : mOptions.entries()) { - optionSetter.setOptionValue(e.getKey(), e.getValue()); - } + private File createPackageFile(File parentDir, String packageName, String apkName) + throws IOException { + File packageDir = + Files.createDirectories(Paths.get(parentDir.getAbsolutePath(), packageName)) + .toFile(); - return preparer; - } + return Files.createFile(Paths.get(packageDir.getAbsolutePath(), apkName)).toFile(); } } diff --git a/harness/src/test/java/com/android/compatibility/targetprep/CheckGmsPreparerTest.java b/harness/src/test/java/com/android/compatibility/targetprep/CheckGmsPreparerTest.java deleted file mode 100644 index 09f2141..0000000 --- a/harness/src/test/java/com/android/compatibility/targetprep/CheckGmsPreparerTest.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.compatibility.targetprep; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import com.android.ddmlib.Log; -import com.android.ddmlib.Log.ILogOutput; -import com.android.ddmlib.Log.LogLevel; -import com.android.tradefed.build.BuildInfo; -import com.android.tradefed.config.OptionSetter; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.invoker.InvocationContext; -import com.android.tradefed.invoker.TestInformation; -import com.android.tradefed.targetprep.TargetSetupError; -import com.android.tradefed.util.CommandResult; -import com.android.tradefed.util.CommandStatus; - -import com.google.common.truth.Correspondence; - -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; - -import java.util.ArrayList; - -@RunWith(JUnit4.class) -public final class CheckGmsPreparerTest { - - private CheckGmsPreparer mPreparer; - private LogCaptor mLogCaptor; - - @Before - public void setUp() throws Exception { - mPreparer = new CheckGmsPreparer(); - new OptionSetter(mPreparer).setOptionValue(CheckGmsPreparer.OPTION_ENABLE, "true"); - - mLogCaptor = new LogCaptor(); - Log.addLogger(mLogCaptor); - } - - @After - public void tearDown() { - Log.removeLogger(mLogCaptor); - } - - @Test - public void setUp_checkDisabledAndGmsAbsent_doesNotReboot() throws Exception { - ITestDevice device = createDeviceWithGmsAbsent(); - disablePreparer(mPreparer); - - mPreparer.setUp(createTestInfo(device)); - - Mockito.verify(device, Mockito.never()).reboot(); - assertThat(mLogCaptor.getLogItems()) - .comparingElementsUsing(createContainsErrorLogCorrespondence()) - .doesNotContain("GMS"); - } - - @Test - public void tearDown_checkDisabledAndGmsAbsent_doesNotLog() throws Exception { - ITestDevice device = createDeviceWithGmsAbsent(); - disablePreparer(mPreparer); - - mPreparer.tearDown(createTestInfo(device), null); - - assertThat(mLogCaptor.getLogItems()) - .comparingElementsUsing(createContainsErrorLogCorrespondence()) - .doesNotContain("GMS"); - } - - @Test - public void tearDown_setUpThrows_doesNotCheck() throws Exception { - ITestDevice device = createDeviceWithGmsAbsent(); - TestInformation testInfo = createTestInfo(device); - assertThrows(TargetSetupError.class, () -> mPreparer.setUp(testInfo)); - mLogCaptor.reset(); - Mockito.reset(device); - Mockito.when(device.executeShellV2Command(Mockito.any())) - .thenReturn(createFailedCommandResult()); - - mPreparer.tearDown(testInfo, null); - - Mockito.verify(device, Mockito.never()).executeShellV2Command(Mockito.any()); - } - - @Test - public void tearDown_setUpRecoveredGms_checksGms() throws Exception { - ITestDevice device = createDeviceWithGmsAbsentAndRecoverable(); - TestInformation testInfo = createTestInfo(device); - mPreparer.setUp(testInfo); - mLogCaptor.reset(); - Mockito.reset(device); - Mockito.when(device.executeShellV2Command(CheckGmsPreparer.CHECK_GMS_COMMAND)) - .thenReturn(createSuccessfulCommandResult()); - - mPreparer.tearDown(testInfo, null); - - Mockito.verify(device, Mockito.atLeast(1)) - .executeShellV2Command(CheckGmsPreparer.CHECK_GMS_COMMAND); - } - - @Test - public void tearDown_setUpFoundGms_checksGms() throws Exception { - ITestDevice device = createDeviceWithGmsPresent(); - TestInformation testInfo = createTestInfo(device); - mPreparer.setUp(testInfo); - Mockito.reset(device); - mLogCaptor.reset(); - Mockito.when(device.executeShellV2Command(CheckGmsPreparer.CHECK_GMS_COMMAND)) - .thenReturn(createSuccessfulCommandResult()); - - mPreparer.tearDown(testInfo, null); - - Mockito.verify(device, Mockito.atLeast(1)) - .executeShellV2Command(CheckGmsPreparer.CHECK_GMS_COMMAND); - } - - @Test - public void setUp_gmsPresent_doesNotReboot() throws Exception { - ITestDevice device = createDeviceWithGmsPresent(); - - mPreparer.setUp(createTestInfo(device)); - - Mockito.verify(device, Mockito.never()).reboot(); - assertThat(mLogCaptor.getLogItems()) - .comparingElementsUsing(createContainsErrorLogCorrespondence()) - .doesNotContain("GMS"); - } - - @Test - public void setUp_gmsProcessRecoveredAfterReboot_doesNotThrow() throws Exception { - ITestDevice device = createDeviceWithGmsAbsentAndRecoverable(); - - mPreparer.setUp(createTestInfo(device)); - - Mockito.verify(device, Mockito.times(1)).reboot(); - assertThat(mLogCaptor.getLogItems()) - .comparingElementsUsing(createContainsErrorLogCorrespondence()) - .contains("GMS"); - } - - @Test - public void setUp_gmsProcessNotRecoveredAfterReboot_throwsException() throws Exception { - ITestDevice device = createDeviceWithGmsAbsent(); - - assertThrows(TargetSetupError.class, () -> mPreparer.setUp(createTestInfo(device))); - Mockito.verify(device, Mockito.times(1)).reboot(); - assertThat(mLogCaptor.getLogItems()) - .comparingElementsUsing(createContainsErrorLogCorrespondence()) - .contains("GMS"); - } - - @Test - public void tearDown_gmsProcessPresent_doesNotLog() throws Exception { - ITestDevice device = createDeviceWithGmsPresent(); - - mPreparer.tearDown(createTestInfo(device), null); - - assertThat(mLogCaptor.getLogItems()) - .comparingElementsUsing(createContainsErrorLogCorrespondence()) - .doesNotContain("GMS"); - } - - @Test - public void tearDown_gmsProcessAbsent_logsError() throws Exception { - ITestDevice device = createDeviceWithGmsAbsent(); - - mPreparer.tearDown(createTestInfo(device), null); - - assertThat(mLogCaptor.getLogItems()) - .comparingElementsUsing(createContainsErrorLogCorrespondence()) - .contains("GMS"); - } - - private static void disablePreparer(CheckGmsPreparer preparer) throws Exception { - new OptionSetter(preparer).setOptionValue(CheckGmsPreparer.OPTION_ENABLE, "false"); - } - - private static Correspondence<LogItem, String> createContainsErrorLogCorrespondence() { - return Correspondence.from( - (LogItem actual, String expected) -> { - return actual.getLogLevel() == LogLevel.ERROR - && actual.getMessage().contains(expected); - }, - "has an error log that contains"); - } - - private static ITestDevice createDeviceWithGmsAbsentAndRecoverable() throws Exception { - ITestDevice device = Mockito.mock(ITestDevice.class); - Mockito.doReturn(createFailedCommandResult()) - .doReturn(createSuccessfulCommandResult()) - .when(device) - .executeShellV2Command(CheckGmsPreparer.CHECK_GMS_COMMAND); - return device; - } - - private static ITestDevice createDeviceWithGmsPresent() throws Exception { - ITestDevice device = Mockito.mock(ITestDevice.class); - Mockito.when(device.executeShellV2Command(CheckGmsPreparer.CHECK_GMS_COMMAND)) - .thenReturn(createSuccessfulCommandResult()); - return device; - } - - private static ITestDevice createDeviceWithGmsAbsent() throws Exception { - ITestDevice device = Mockito.mock(ITestDevice.class); - Mockito.when(device.executeShellV2Command(CheckGmsPreparer.CHECK_GMS_COMMAND)) - .thenReturn(createFailedCommandResult()); - return device; - } - - private static final class LogCaptor implements ILogOutput { - private ArrayList<LogItem> mLogItems = new ArrayList<>(); - - void reset() { - mLogItems.clear(); - } - - ArrayList<LogItem> getLogItems() { - return mLogItems; - } - - @Override - public void printLog(LogLevel logLevel, String tag, String message) { - mLogItems.add(new LogItem(logLevel, tag, message)); - } - - @Override - public void printAndPromptLog(LogLevel logLevel, String tag, String message) { - printLog(logLevel, tag, message); - } - } - - private static final class LogItem { - private LogLevel mLogLevel; - private String mMessage; - - LogLevel getLogLevel() { - return mLogLevel; - } - - String getMessage() { - return mMessage; - } - - LogItem(LogLevel logLevel, String tag, String message) { - mLogLevel = logLevel; - mMessage = message; - } - } - - private static TestInformation createTestInfo(ITestDevice device) { - IInvocationContext context = new InvocationContext(); - context.addAllocatedDevice("device1", device); - context.addDeviceBuildInfo("device1", new BuildInfo()); - return TestInformation.newBuilder().setInvocationContext(context).build(); - } - - private static CommandResult createSuccessfulCommandResult() { - CommandResult commandResult = new CommandResult(CommandStatus.SUCCESS); - commandResult.setExitCode(0); - return commandResult; - } - - private static CommandResult createFailedCommandResult() { - CommandResult commandResult = new CommandResult(CommandStatus.FAILED); - commandResult.setExitCode(1); - return commandResult; - } -} diff --git a/harness/src/test/java/com/android/compatibility/testtype/AppLaunchTestTest.java b/harness/src/test/java/com/android/compatibility/testtype/AppLaunchTestTest.java index 0c23cc3..4f9aa35 100644 --- a/harness/src/test/java/com/android/compatibility/testtype/AppLaunchTestTest.java +++ b/harness/src/test/java/com/android/compatibility/testtype/AppLaunchTestTest.java @@ -15,20 +15,16 @@ */ package com.android.compatibility.testtype; -import com.android.tradefed.config.OptionSetter; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; -import com.android.tradefed.result.FileInputStreamSource; import com.android.tradefed.result.ITestInvocationListener; -import com.android.tradefed.result.InputStreamSource; import com.android.tradefed.result.TestDescription; import com.android.tradefed.testtype.InstrumentationTest; import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -43,13 +39,10 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.InOrder; -import org.mockito.Mockito; import java.util.HashMap; import java.util.HashSet; @@ -62,7 +55,6 @@ public final class AppLaunchTestTest { private final ITestInvocationListener mMockListener = mock(ITestInvocationListener.class); private static final String TEST_PACKAGE_NAME = "package_name"; private static final TestInformation NULL_TEST_INFORMATION = null; - @Rule public final TemporaryFolder tempFolder = new TemporaryFolder(); @Test public void run_testFailed() throws DeviceNotAvailableException { @@ -85,28 +77,10 @@ public final class AppLaunchTestTest { } @Test - public void run_takeScreenShot_savesToTestLog() throws Exception { - InstrumentationTest instrumentationTest = createPassingInstrumentationTest(); - AppLaunchTest appLaunchTest = createLaunchTestWithInstrumentation(instrumentationTest); - new OptionSetter(appLaunchTest) - .setOptionValue(AppLaunchTest.SCREENSHOT_AFTER_LAUNCH, "true"); - ITestDevice mMockDevice = mock(ITestDevice.class); - appLaunchTest.setDevice(mMockDevice); - InputStreamSource screenshotData = new FileInputStreamSource(tempFolder.newFile()); - when(mMockDevice.getScreenshot()).thenReturn(screenshotData); - when(mMockDevice.getSerialNumber()).thenReturn("SERIAL"); - - appLaunchTest.run(NULL_TEST_INFORMATION, mMockListener); - - Mockito.verify(mMockListener, times(1)) - .testLog(Mockito.contains("screenshot"), Mockito.any(), Mockito.eq(screenshotData)); - } - - @Test public void run_packageResetSuccess() throws DeviceNotAvailableException { ITestDevice mMockDevice = mock(ITestDevice.class); when(mMockDevice.executeShellV2Command(String.format("pm clear %s", TEST_PACKAGE_NAME))) - .thenReturn(createSuccessfulCommandResult()); + .thenReturn(new CommandResult(CommandStatus.SUCCESS)); AppLaunchTest appLaunchTest = createLaunchTestWithMockDevice(mMockDevice); appLaunchTest.run(NULL_TEST_INFORMATION, mMockListener); @@ -118,7 +92,7 @@ public final class AppLaunchTestTest { public void run_packageResetError() throws DeviceNotAvailableException { ITestDevice mMockDevice = mock(ITestDevice.class); when(mMockDevice.executeShellV2Command(String.format("pm clear %s", TEST_PACKAGE_NAME))) - .thenReturn(createFailedCommandResult()); + .thenReturn(new CommandResult(CommandStatus.FAILED)); AppLaunchTest appLaunchTest = createLaunchTestWithMockDevice(mMockDevice); appLaunchTest.run(NULL_TEST_INFORMATION, mMockListener); @@ -129,7 +103,7 @@ public final class AppLaunchTestTest { @Test public void run_testRetry_passedAfterTwoFailings() throws Exception { InstrumentationTest instrumentationTest = createPassingInstrumentationTestAfterFailing(2); - AppLaunchTest appLaunchTest = createLaunchTestWithInstrumentation(instrumentationTest, 2); + AppLaunchTest appLaunchTest = createLaunchTestWithRetry(instrumentationTest, 2); appLaunchTest.run(NULL_TEST_INFORMATION, mMockListener); @@ -139,7 +113,7 @@ public final class AppLaunchTestTest { @Test public void run_testRetry_failedAfterThreeFailings() throws Exception { InstrumentationTest instrumentationTest = createPassingInstrumentationTestAfterFailing(3); - AppLaunchTest appLaunchTest = createLaunchTestWithInstrumentation(instrumentationTest, 2); + AppLaunchTest appLaunchTest = createLaunchTestWithRetry(instrumentationTest, 2); appLaunchTest.run(NULL_TEST_INFORMATION, mMockListener); @@ -395,10 +369,24 @@ public final class AppLaunchTestTest { } private AppLaunchTest createLaunchTestWithInstrumentation(InstrumentationTest instrumentation) { - return createLaunchTestWithInstrumentation(instrumentation, 0); + AppLaunchTest appLaunchTest = + new AppLaunchTest(TEST_PACKAGE_NAME) { + @Override + protected InstrumentationTest createInstrumentationTest( + String packageBeingTested) { + return instrumentation; + } + + @Override + protected CommandResult resetPackage() throws DeviceNotAvailableException { + return new CommandResult(CommandStatus.SUCCESS); + } + }; + appLaunchTest.setDevice(mock(ITestDevice.class)); + return appLaunchTest; } - private AppLaunchTest createLaunchTestWithInstrumentation( + private AppLaunchTest createLaunchTestWithRetry( InstrumentationTest instrumentation, int retryCount) { AppLaunchTest appLaunchTest = new AppLaunchTest(TEST_PACKAGE_NAME, retryCount) { @@ -410,7 +398,7 @@ public final class AppLaunchTestTest { @Override protected CommandResult resetPackage() throws DeviceNotAvailableException { - return createSuccessfulCommandResult(); + return new CommandResult(CommandStatus.SUCCESS); } }; appLaunchTest.setDevice(mock(ITestDevice.class)); @@ -418,7 +406,7 @@ public final class AppLaunchTestTest { } private AppLaunchTest createLaunchTestWithMockDevice(ITestDevice device) { - AppLaunchTest appLaunchTest = new AppLaunchTest(TEST_PACKAGE_NAME, 0); + AppLaunchTest appLaunchTest = new AppLaunchTest(TEST_PACKAGE_NAME); appLaunchTest.setDevice(device); return appLaunchTest; } @@ -442,16 +430,4 @@ public final class AppLaunchTestTest { .testEnded(anyObject(), anyLong(), (Map<String, String>) any()); inOrder.verify(listener, times(1)).testRunEnded(anyLong(), (HashMap<String, Metric>) any()); } - - private CommandResult createSuccessfulCommandResult() { - CommandResult commandResult = new CommandResult(CommandStatus.SUCCESS); - commandResult.setExitCode(0); - return commandResult; - } - - private CommandResult createFailedCommandResult() { - CommandResult commandResult = new CommandResult(CommandStatus.FAILED); - commandResult.setExitCode(1); - return commandResult; - } } diff --git a/harness/src/test/java/com/android/csuite/config/AppRemoteFileResolverTest.java b/harness/src/test/java/com/android/csuite/config/AppRemoteFileResolverTest.java deleted file mode 100644 index 01c6ecf..0000000 --- a/harness/src/test/java/com/android/csuite/config/AppRemoteFileResolverTest.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.csuite.config; - -import static com.android.csuite.testing.Correspondences.instanceOf; -import static com.android.csuite.testing.MoreAsserts.assertThrows; - -import static com.google.common.truth.Truth.assertThat; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import com.android.tradefed.build.BuildRetrievalError; -import com.android.tradefed.config.ConfigurationException; -import com.android.tradefed.config.Option; -import com.android.tradefed.config.OptionSetter; -import com.android.tradefed.config.remote.IRemoteFileResolver; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.testing.NullPointerTester; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ServiceLoader; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; - -@RunWith(JUnit4.class) -public final class AppRemoteFileResolverTest { - - private static final String PACKAGE_NAME = "com.example.app"; - private static final File APP_URI_FILE = uriToFile("app://" + PACKAGE_NAME); - private static final ImmutableMap<String, String> EMPTY_PARAMS = ImmutableMap.of(); - - @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); - - // Class sanity tests. - - @Test - public void isServiceLoadable() throws Exception { - ClassLoader classLoader = classLoaderWithProviders(AppRemoteFileResolver.class.getName()); - - ServiceLoader<IRemoteFileResolver> serviceLoader = - ServiceLoader.load(IRemoteFileResolver.class, classLoader); - - // Copy the list to provide better failure error messages since ServiceLoader's string - // representation is not very informative. - assertThat(ImmutableList.copyOf(serviceLoader)) - .comparingElementsUsing(instanceOf()) - .contains(AppRemoteFileResolver.class); - } - - @Test - public void nullPointers() { - NullPointerTester tester = new NullPointerTester(); - tester.setDefault(File.class, APP_URI_FILE); - tester.testAllPublicConstructors(AppRemoteFileResolver.class); - tester.testAllPublicInstanceMethods(new AppRemoteFileResolver()); - } - - // URI validation tests. - - @Test - public void unsupportedUriScheme_throwsException() throws Exception { - AppRemoteFileResolver resolver = newResolverWithAnyTemplate(); - String uri = "gs://" + PACKAGE_NAME; - File f = uriToFile(uri); - - Throwable thrown = - assertThrows( - IllegalArgumentException.class, - () -> resolver.resolveRemoteFiles(f, EMPTY_PARAMS)); - - assertThat(thrown).hasMessageThat().contains("(gs)"); - assertThat(thrown).hasMessageThat().contains(uri); - } - - @Test - public void opaqueUri_throwsException() throws Exception { - AppRemoteFileResolver resolver = newResolverWithAnyTemplate(); - File uri = uriToFile("app:" + PACKAGE_NAME); - - Throwable thrown = - assertThrows( - IllegalArgumentException.class, - () -> resolver.resolveRemoteFiles(uri, EMPTY_PARAMS)); - - assertThat(thrown).hasMessageThat().contains("package name"); - } - - @Test - public void uriHasPathComponent_throwsException() throws Exception { - AppRemoteFileResolver resolver = newResolverWithAnyTemplate(); - File uri = uriToFile("app://" + PACKAGE_NAME + "/invalid"); - - Throwable thrown = - assertThrows( - IllegalArgumentException.class, - () -> resolver.resolveRemoteFiles(uri, EMPTY_PARAMS)); - - assertThat(thrown).hasMessageThat().contains("invalid"); - } - - // Template validation and expansion tests. - - @Test - public void templateNotSet_returnsNull() throws Exception { - AppRemoteFileResolver resolver = new AppRemoteFileResolver(); - - File actual = resolver.resolveRemoteFiles(APP_URI_FILE, EMPTY_PARAMS); - - assertThat(actual).isNull(); - } - - @Test - public void emptyTemplate_throwsException() throws Exception { - AppRemoteFileResolver resolver = newResolverWithTemplate(""); - - Throwable thrown = - assertThrows( - IllegalStateException.class, - () -> resolver.resolveRemoteFiles(APP_URI_FILE, EMPTY_PARAMS)); - - assertThat(thrown).hasMessageThat().contains(AppRemoteFileResolver.URI_TEMPLATE_OPTION); - } - - @Test - public void templateHasNoPlaceholders_returnsFileWithoutExpansion() throws Exception { - File expected = temporaryFolder.newFolder(); - AppRemoteFileResolver resolver = newResolverWithTemplate(expected.toURI().toString()); - - File actual = resolver.resolveRemoteFiles(APP_URI_FILE, EMPTY_PARAMS); - - assertThat(actual).isEqualTo(expected); - } - - @Test - public void templateContainsPlaceholderForUndefinedVar_throwsException() throws Exception { - AppRemoteFileResolver resolver = newResolverWithTemplate("file://{undefined}"); - - Throwable thrown = - assertThrows( - IllegalStateException.class, - () -> resolver.resolveRemoteFiles(APP_URI_FILE, EMPTY_PARAMS)); - - assertThat(thrown).hasMessageThat().contains("undefined"); - } - - @Test - public void templateExpandsToInvalidUri_throwsException() throws Exception { - AppRemoteFileResolver resolver = newResolverWithTemplate("file:\\{package}"); - - Throwable thrown = - assertThrows( - IllegalStateException.class, - () -> resolver.resolveRemoteFiles(APP_URI_FILE, EMPTY_PARAMS)); - - assertThat(thrown).hasMessageThat().contains(AppRemoteFileResolver.URI_TEMPLATE_OPTION); - } - - @Test - public void templateContainsPlaceholder_resolvesUriToFile() throws Exception { - File parent = temporaryFolder.newFolder(); - File expected = new File(parent, PACKAGE_NAME); - String template = new File(parent, "{package}").toString(); - AppRemoteFileResolver resolver = newResolverWithTemplate(template); - File uri = uriToFile("app://" + PACKAGE_NAME); - - File actual = resolver.resolveRemoteFiles(uri, EMPTY_PARAMS); - - assertThat(actual).isEqualTo(expected); - } - - @Test - public void templateExpandsToAppUri_throwsException() throws Exception { - AppRemoteFileResolver resolver = newResolverWithTemplate("app://{package}"); - - Throwable thrown = - assertThrows( - BuildRetrievalError.class, - () -> resolver.resolveRemoteFiles(APP_URI_FILE, EMPTY_PARAMS)); - - assertThat(thrown).hasMessageThat().contains("'app'"); - } - - @Test - public void templateExpandsToUriWithUnsupportedScheme_returnsExpandedUri() throws Exception { - String uri = "unsupported://" + PACKAGE_NAME; - AppRemoteFileResolver resolver = newResolverWithTemplate(uri); - File expected = uriToFile(uri); - - File actual = resolver.resolveRemoteFiles(APP_URI_FILE, EMPTY_PARAMS); - - assertThat(actual).isEqualTo(expected); - } - - // Utility classes and methods. - - /** - * Constructs a File from a URI string using the same logic TradeFed uses since it's tricky and - * has some gotchas such as stripping slashes. - */ - private static File uriToFile(String str) { - FileOptionSource optionSource = new FileOptionSource(); - - try { - OptionSetter setter = new OptionSetter(optionSource); - setter.setOptionValue(FileOptionSource.OPTION_NAME, str); - } catch (ConfigurationException e) { - throw new RuntimeException(e); - } - - return optionSource.file; - } - - private static final class FileOptionSource { - static final String OPTION_NAME = "file"; - - @Option(name = OPTION_NAME) - public File file; - } - - private static AppRemoteFileResolver newResolverWithAnyTemplate() - throws ConfigurationException { - return newResolverWithTemplate("file:///tmp/{package}"); - } - - private static AppRemoteFileResolver newResolverWithTemplate(String uriTemplate) - throws ConfigurationException { - AppRemoteFileResolver resolver = new AppRemoteFileResolver(); - OptionSetter setter = new OptionSetter(resolver); - setter.setOptionValue("app:" + AppRemoteFileResolver.URI_TEMPLATE_OPTION, uriTemplate); - return resolver; - } - - private ClassLoader classLoaderWithProviders(String... lines) throws IOException { - String service = IRemoteFileResolver.class.getName(); - File jar = temporaryFolder.newFile(); - - try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar))) { - JarEntry jarEntry = new JarEntry("META-INF/services/" + service); - - out.putNextEntry(jarEntry); - PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, UTF_8)); - - for (String line : lines) { - writer.println(line); - } - - writer.flush(); - } - - return new URLClassLoader(new URL[] {jar.toURI().toURL()}); - } -} diff --git a/harness/src/test/java/com/android/csuite/config/ModuleGeneratorTest.java b/harness/src/test/java/com/android/csuite/config/ModuleGeneratorTest.java deleted file mode 100644 index a679828..0000000 --- a/harness/src/test/java/com/android/csuite/config/ModuleGeneratorTest.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.csuite.config; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import com.android.csuite.core.PackageNameProvider; -import com.android.tradefed.build.BuildInfo; -import com.android.tradefed.config.Configuration; -import com.android.tradefed.config.IConfiguration; -import com.android.tradefed.config.OptionSetter; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.invoker.InvocationContext; -import com.android.tradefed.invoker.TestInformation; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ListMultimap; -import com.google.common.jimfs.Jimfs; -import com.google.common.truth.IterableSubject; -import com.google.common.truth.StringSubject; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mockito; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -@RunWith(JUnit4.class) -public final class ModuleGeneratorTest { - private static final String TEST_PACKAGE_NAME1 = "test.package.name1"; - private static final String TEST_PACKAGE_NAME2 = "test.package.name2"; - private static final String PACKAGE_PLACEHOLDER = "{package}"; - private static final Exception NO_EXCEPTION = null; - - private final FileSystem mFileSystem = - Jimfs.newFileSystem(com.google.common.jimfs.Configuration.unix()); - - @Test - public void tearDown_packageNamesProvided_deletesGeneratedModules() throws Exception { - Path testsDir = createTestsDir(); - ModuleGenerator generator1 = - createGeneratorBuilder() - .setTestsDir(testsDir) - .addPackage(TEST_PACKAGE_NAME1) - .addPackage(TEST_PACKAGE_NAME2) - .build(); - generator1.split(); - ModuleGenerator generator2 = - createGeneratorBuilder() - .setTestsDir(testsDir) - .addPackage(TEST_PACKAGE_NAME1) - .addPackage(TEST_PACKAGE_NAME2) - .build(); - - generator2.tearDown(createTestInfo(), NO_EXCEPTION); - - assertThatListDirectory(testsDir).isEmpty(); - } - - @Test - public void tearDown_packageNamesNotProvided_doesNotThrowError() throws Exception { - ModuleGenerator generator = createGeneratorBuilder().setTestsDir(createTestsDir()).build(); - generator.split(); - - generator.tearDown(createTestInfo(), NO_EXCEPTION); - } - - @Test - public void split_packageNameIsEmptyString_throwsError() throws Exception { - ModuleGenerator generator = createGeneratorBuilder().addPackage("").build(); - - assertThrows(IllegalArgumentException.class, () -> generator.split()); - } - - @Test - public void split_packageNameContainsPlaceholder_throwsError() throws Exception { - ModuleGenerator generator = - createGeneratorBuilder().addPackage("a" + PACKAGE_PLACEHOLDER + "b").build(); - - assertThrows(IllegalArgumentException.class, () -> generator.split()); - } - - @Test - public void split_multiplePackageNameProviders_generateModulesForAll() throws Exception { - Path testsDir = createTestsDir(); - ModuleGenerator generator = - createGeneratorBuilder() - .setTestsDir(testsDir) - .addPackageNameProvider(() -> ImmutableSet.of(TEST_PACKAGE_NAME1)) - // Simulates package providers providing duplicated package names. - .addPackageNameProvider(() -> ImmutableSet.of(TEST_PACKAGE_NAME1)) - .addPackageNameProvider(() -> ImmutableSet.of(TEST_PACKAGE_NAME2)) - .build(); - - generator.split(); - - assertThatListDirectory(testsDir) - .containsExactly( - getModuleConfigFile(testsDir, TEST_PACKAGE_NAME1), - getModuleConfigFile(testsDir, TEST_PACKAGE_NAME2)); - } - - @Test - public void split_packageNameProviderThrowsException_throwsException() throws Exception { - Path testsDir = createTestsDir(); - ModuleGenerator generator = - createGeneratorBuilder() - .setTestsDir(testsDir) - .addPackageNameProvider( - () -> { - throw new IOException(); - }) - .build(); - - assertThrows(UncheckedIOException.class, () -> generator.split()); - } - - @Test - public void split_packageNamesNotProvided_doesNotGenerate() throws Exception { - Path testsDir = createTestsDir(); - ModuleGenerator generator = createGeneratorBuilder().setTestsDir(testsDir).build(); - - generator.split(); - - assertThatListDirectory(testsDir).isEmpty(); - } - - @Test - public void split_templateContainsPlaceholders_replacesPlaceholdersInOutput() throws Exception { - Path testsDir = createTestsDir(); - String content = "hello placeholder%s%s world"; - ModuleGenerator generator = - createGeneratorBuilder() - .setTestsDir(testsDir) - .addPackage(TEST_PACKAGE_NAME1) - .addPackage(TEST_PACKAGE_NAME2) - .setTemplateContent( - String.format(content, PACKAGE_PLACEHOLDER, PACKAGE_PLACEHOLDER)) - .build(); - - generator.split(); - - assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME1) - .isEqualTo(String.format(content, TEST_PACKAGE_NAME1, TEST_PACKAGE_NAME1)); - assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME2) - .isEqualTo(String.format(content, TEST_PACKAGE_NAME2, TEST_PACKAGE_NAME2)); - } - - @Test - public void split_templateDoesNotContainPlaceholder_outputsTemplateContent() throws Exception { - Path testsDir = createTestsDir(); - String content = "no placeholder"; - ModuleGenerator generator = - createGeneratorBuilder() - .setTestsDir(testsDir) - .addPackage(TEST_PACKAGE_NAME1) - .addPackage(TEST_PACKAGE_NAME2) - .setTemplateContent(content) - .build(); - - generator.split(); - - assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME1).isEqualTo(content); - assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME2).isEqualTo(content); - } - - @Test - public void split_templateContentIsEmpty_outputsTemplateContent() throws Exception { - Path testsDir = createTestsDir(); - String content = ""; - ModuleGenerator generator = - createGeneratorBuilder() - .setTestsDir(testsDir) - .addPackage(TEST_PACKAGE_NAME1) - .addPackage(TEST_PACKAGE_NAME2) - .setTemplateContent(content) - .build(); - - generator.split(); - - assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME1).isEqualTo(content); - assertThatModuleConfigFileContent(testsDir, TEST_PACKAGE_NAME2).isEqualTo(content); - } - - private static StringSubject assertThatModuleConfigFileContent( - Path testsDir, String packageName) throws IOException { - return assertThat( - new String(Files.readAllBytes(getModuleConfigFile(testsDir, packageName)))); - } - - private static IterableSubject assertThatListDirectory(Path dir) throws IOException { - // Convert stream to list because com.google.common.truth.Truth8 is not available. - return assertThat( - Files.walk(dir) - .filter(p -> !p.equals(dir)) - .collect(ImmutableList.toImmutableList())); - } - - private static Path getModuleConfigFile(Path baseDir, String packageName) { - return baseDir.resolve(packageName + ".config"); - } - - private Path createTestsDir() throws IOException { - Path rootPath = mFileSystem.getPath("csuite"); - Files.createDirectories(rootPath); - return Files.createTempDirectory(rootPath, "testDir"); - } - - private static TestInformation createTestInfo() { - IInvocationContext context = new InvocationContext(); - context.addAllocatedDevice("device1", Mockito.mock(ITestDevice.class)); - context.addDeviceBuildInfo("device1", new BuildInfo()); - return TestInformation.newBuilder().setInvocationContext(context).build(); - } - - private GeneratorBuilder createGeneratorBuilder() throws IOException { - return new GeneratorBuilder() - .setFileSystem(mFileSystem) - .setTemplateContent(MODULE_TEMPLATE_CONTENT) - .setOption(ModuleGenerator.OPTION_TEMPLATE, "empty_path"); - } - - private static final class GeneratorBuilder { - private final ListMultimap<String, String> mOptions = ArrayListMultimap.create(); - private final Set<String> mPackages = new HashSet<>(); - private final List<PackageNameProvider> mPackageNameProviders = new ArrayList<>(); - private Path mTestsDir; - private String mTemplateContent; - private FileSystem mFileSystem; - - GeneratorBuilder addPackage(String packageName) { - mPackages.add(packageName); - return this; - } - - GeneratorBuilder addPackageNameProvider(PackageNameProvider packageNameProvider) { - mPackageNameProviders.add(packageNameProvider); - return this; - } - - GeneratorBuilder setFileSystem(FileSystem fileSystem) { - mFileSystem = fileSystem; - return this; - } - - GeneratorBuilder setTemplateContent(String templateContent) { - mTemplateContent = templateContent; - return this; - } - - GeneratorBuilder setTestsDir(Path testsDir) { - mTestsDir = testsDir; - return this; - } - - GeneratorBuilder setOption(String key, String value) { - mOptions.put(key, value); - return this; - } - - ModuleGenerator build() throws Exception { - ModuleGenerator generator = - new ModuleGenerator( - mFileSystem, buildInfo -> mTestsDir, resourcePath -> mTemplateContent); - - OptionSetter optionSetter = new OptionSetter(generator); - for (Map.Entry<String, String> entry : mOptions.entries()) { - optionSetter.setOptionValue(entry.getKey(), entry.getValue()); - } - - List<PackageNameProvider> packageNameProviders = new ArrayList<>(mPackageNameProviders); - packageNameProviders.add(() -> mPackages); - - IConfiguration configuration = new Configuration("name", "description"); - configuration.setConfigurationObjectList( - ModuleGenerator.PACKAGE_NAME_PROVIDER, packageNameProviders); - generator.setConfiguration(configuration); - - return generator; - } - } - - private static final String MODULE_TEMPLATE_CONTENT = - "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - + "<configuration description=\"description\">\n" - + " <option name=\"package-name\" value=\"{package}\"/>\n" - + " <target_generator class=\"some.generator.class\">\n" - + " <option name=\"test-file-name\" value=\"app://{package}\"/>\n" - + " </target_generator>\n" - + " <test class=\"some.test.class\"/>\n" - + "</configuration>"; -} diff --git a/harness/src/test/java/com/android/csuite/core/CommandLinePackageNameProviderTest.java b/harness/src/test/java/com/android/csuite/core/CommandLinePackageNameProviderTest.java deleted file mode 100644 index 41e6dc1..0000000 --- a/harness/src/test/java/com/android/csuite/core/CommandLinePackageNameProviderTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2021 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.csuite.core; - -import static com.google.common.truth.Truth.assertThat; - -import com.android.tradefed.config.OptionSetter; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.util.Set; - -@RunWith(JUnit4.class) -public final class CommandLinePackageNameProviderTest { - - @Test - public void get_packageNamesProvided_returnsPackageNames() throws Exception { - CommandLinePackageNameProvider provider = new CommandLinePackageNameProvider(); - String package1 = "package.name1"; - String package2 = "package.name2"; - OptionSetter optionSetter = new OptionSetter(provider); - optionSetter.setOptionValue(CommandLinePackageNameProvider.PACKAGE, package1); - optionSetter.setOptionValue(CommandLinePackageNameProvider.PACKAGE, package2); - - Set<String> packageNames = provider.get(); - - assertThat(packageNames).containsExactly(package1, package2); - } -} diff --git a/harness/src/test/java/com/android/csuite/core/FileBasedPackageNameProviderTest.java b/harness/src/test/java/com/android/csuite/core/FileBasedPackageNameProviderTest.java deleted file mode 100644 index ee429b2..0000000 --- a/harness/src/test/java/com/android/csuite/core/FileBasedPackageNameProviderTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.csuite.core; - -import static com.google.common.truth.Truth.assertThat; - -import com.android.tradefed.config.ConfigurationException; -import com.android.tradefed.config.OptionSetter; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Set; - -@RunWith(JUnit4.class) -public final class FileBasedPackageNameProviderTest { - private static final String TEST_PACKAGE_NAME1 = "test.package.name1"; - private static final String TEST_PACKAGE_NAME2 = "test.package.name2"; - private static final String PACKAGE_PLACEHOLDER = "{package}"; - private static final Exception NO_EXCEPTION = null; - - @Rule public final TemporaryFolder tempFolder = new TemporaryFolder(); - - @Test - public void get_fileNotSpecified_returnsEmptySet() throws Exception { - FileBasedPackageNameProvider provider = createProvider(); - - Set<String> packageNames = provider.get(); - - assertThat(packageNames).isEmpty(); - } - - @Test - public void get_multipleFileSpecified_returnsAllEntries() throws Exception { - String packageName1 = "a"; - String packageName2 = "b"; - String packageName3 = "c"; - String packageName4 = "d"; - FileBasedPackageNameProvider provider = - createProvider( - createPackagesFile(packageName1 + "\n" + packageName2), - createPackagesFile(packageName3 + "\n" + packageName4)); - - Set<String> packageNames = provider.get(); - - assertThat(packageNames) - .containsExactly(packageName1, packageName2, packageName3, packageName4); - } - - @Test - public void get_fileContainsEmptyLines_ignoresEmptyLines() throws Exception { - String packageName1 = "a"; - String packageName2 = "b"; - FileBasedPackageNameProvider provider = - createProvider(createPackagesFile(packageName1 + "\n \n\n" + packageName2)); - - Set<String> packageNames = provider.get(); - - assertThat(packageNames).containsExactly(packageName1, packageName2); - } - - @Test - public void get_fileContainsCommentLines_ignoresCommentLines() throws Exception { - String packageName1 = "a"; - String packageName2 = "b"; - FileBasedPackageNameProvider provider = - createProvider( - createPackagesFile( - packageName1 - + "\n" - + FileBasedPackageNameProvider.COMMENT_LINE_PREFIX - + " Some comments\n" - + packageName2)); - - Set<String> packageNames = provider.get(); - - assertThat(packageNames).containsExactly(packageName1, packageName2); - } - - private FileBasedPackageNameProvider createProvider(Path... packagesFiles) - throws IOException, ConfigurationException { - FileBasedPackageNameProvider provider = new FileBasedPackageNameProvider(); - OptionSetter optionSetter = new OptionSetter(provider); - for (Path packagesFile : packagesFiles) { - optionSetter.setOptionValue( - FileBasedPackageNameProvider.PACKAGES_FILE, packagesFile.toString()); - } - return provider; - } - - private Path createPackagesFile(String content) throws IOException { - Path tempFile = Files.createTempFile(tempFolder.getRoot().toPath(), "packages", ".txt"); - Files.write(tempFile, content.getBytes()); - return tempFile; - } -} diff --git a/harness/src/test/java/com/android/csuite/core/SystemAppUninstallerTest.java b/harness/src/test/java/com/android/csuite/core/SystemAppUninstallerTest.java deleted file mode 100644 index 30b77db..0000000 --- a/harness/src/test/java/com/android/csuite/core/SystemAppUninstallerTest.java +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.csuite.core; - -import static org.testng.Assert.assertThrows; - -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.targetprep.TargetSetupError; -import com.android.tradefed.util.CommandResult; -import com.android.tradefed.util.CommandStatus; - - - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; - -@RunWith(JUnit4.class) -public final class SystemAppUninstallerTest { - private static final ITestDevice NULL_DEVICE = null; - private static final String TEST_PACKAGE_NAME = "test.package.name"; - private static final String SYSTEM_APP_INSTALL_DIRECTORY = "/system/app"; - private static final String CHECK_PACKAGE_INSTALLED_COMMAND_PREFIX = "pm list packages "; - private static final String GET_PACKAGE_INSTALL_PATH_COMMAND_PREFIX = "pm path "; - private static final String REMOVE_SYSTEM_APP_COMMAND_PREFIX = - "rm -r " + SYSTEM_APP_INSTALL_DIRECTORY; - private static final String REMOVE_APP_DATA_COMMAND_PREFIX = "rm -r /data/data"; - private static final String MOUNT_COMMAND_PREFIX = "mount"; - - @Test - public void uninstallPackage_packageNameIsNull_throws() throws Exception { - assertThrows( - NullPointerException.class, - () -> - SystemPackageUninstaller.uninstallPackage( - null, createGoodDeviceWithAppNotInstalled())); - } - - @Test - public void uninstallPackage_frameworkNotRunning_startsFrameworkOrThrows() throws Exception { - ITestDevice device = createGoodDeviceWithAppNotInstalled(); - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.eq(SystemPackageUninstaller.PM_CHECK_COMMAND))) - .thenReturn(createFailedCommandResult()); - - assertThrows( - TargetSetupError.class, - () -> SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device)); - Mockito.verify(device, Mockito.times(1)).executeShellV2Command(Mockito.eq("start")); - } - - @Test - public void uninstallPackage_packageIsNotInstalled_doesNotRemove() throws Exception { - ITestDevice device = createGoodDeviceWithAppNotInstalled(); - - SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device); - - Mockito.verify(device, Mockito.times(0)).executeShellV2Command(Mockito.startsWith("rm")); - } - - @Test - public void uninstallPackage_differentPackageWithSameNamePrefixInstalled_doesNotRemove() - throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - // Mock the device as if the test package does not exist on device - CommandResult commandResult = createSuccessfulCommandResult(); - commandResult.setStdout(String.format("package:%s_some_more_chars", TEST_PACKAGE_NAME)); - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith( - CHECK_PACKAGE_INSTALLED_COMMAND_PREFIX))) - .thenReturn(commandResult); - - SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device); - - Mockito.verify(device, Mockito.times(0)).executeShellV2Command(Mockito.startsWith("rm")); - } - - @Test - public void uninstallPackage_checkPackageInstalledCommandFailed_throws() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith( - CHECK_PACKAGE_INSTALLED_COMMAND_PREFIX))) - .thenReturn(createFailedCommandResult()); - - assertThrows( - TargetSetupError.class, - () -> SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device)); - } - - @Test - public void uninstallPackage_getInstallDirectoryCommandFailed_throws() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith( - GET_PACKAGE_INSTALL_PATH_COMMAND_PREFIX))) - .thenReturn(createFailedCommandResult()); - - assertThrows( - TargetSetupError.class, - () -> SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device)); - } - - @Test - public void uninstallPackage_packageIsNotSystemApp_doesNotRemove() throws Exception { - ITestDevice device = createGoodDeviceWithUserAppInstalled(); - - SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device); - - Mockito.verify(device, Mockito.times(0)).executeShellV2Command(Mockito.startsWith("rm")); - } - - @Test - public void uninstallPackage_adbAlreadyRooted_doesNotRootAgain() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.when(device.isAdbRoot()).thenReturn(true); - - SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device); - - Mockito.verify(device, Mockito.times(0)).enableAdbRoot(); - } - - @Test - public void uninstallPackage_adbNotAlreadyRooted_rootAdbAndThenUnroot() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.when(device.isAdbRoot()).thenReturn(false); - - SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device); - - Mockito.verify(device, Mockito.times(1)).enableAdbRoot(); - Mockito.verify(device, Mockito.times(1)).disableAdbRoot(); - } - - @Test - public void uninstallPackage_adbRootCommandFailed_throws() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.when(device.enableAdbRoot()).thenThrow(new DeviceNotAvailableException()); - - assertThrows( - DeviceNotAvailableException.class, - () -> SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device)); - } - - @Test - public void uninstallPackage_adbRootFailed_throws() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.when(device.enableAdbRoot()).thenReturn(false); - - assertThrows( - TargetSetupError.class, - () -> SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device)); - } - - @Test - public void uninstallPackage_adbDisableRootCommandFailed_throws() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.when(device.disableAdbRoot()).thenThrow(new DeviceNotAvailableException()); - - assertThrows( - DeviceNotAvailableException.class, - () -> SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device)); - } - - @Test - public void uninstallPackage_adbDisableRootFailed_throws() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.when(device.disableAdbRoot()).thenReturn(false); - - assertThrows( - TargetSetupError.class, - () -> SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device)); - } - - @Test - public void uninstallPackage_adbRemountFailed_throws() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.doThrow(new DeviceNotAvailableException()).when(device).remountSystemWritable(); - - assertThrows( - DeviceNotAvailableException.class, - () -> SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device)); - } - - @Test - public void uninstallPackage_adbRemounted_mountReadOnlyAfterwards() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.doNothing().when(device).remountSystemWritable(); - - SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device); - - Mockito.verify(device, Mockito.times(1)).remountSystemWritable(); - Mockito.verify(device, Mockito.times(1)) - .executeShellV2Command(Mockito.startsWith(MOUNT_COMMAND_PREFIX)); - } - - @Test - public void uninstallPackage_mountReadOnlyFailed_throws() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith(MOUNT_COMMAND_PREFIX))) - .thenReturn(createFailedCommandResult()); - - assertThrows( - TargetSetupError.class, - () -> SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device)); - } - - @Test - public void uninstallPackage_removePackageInstallDirectoryFailed_throws() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith(REMOVE_SYSTEM_APP_COMMAND_PREFIX))) - .thenReturn(createFailedCommandResult()); - - assertThrows( - TargetSetupError.class, - () -> SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device)); - } - - @Test - public void uninstallPackage_removePackageDataDirectoryFailed_doesNotThrow() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith(REMOVE_APP_DATA_COMMAND_PREFIX))) - .thenReturn(createFailedCommandResult()); - - SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device); - } - - @Test - public void uninstallPackage_packageIsSystemApp_appRemoved() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - - SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device); - - Mockito.verify(device, Mockito.times(1)) - .executeShellV2Command(Mockito.startsWith(REMOVE_SYSTEM_APP_COMMAND_PREFIX)); - Mockito.verify(device, Mockito.times(1)) - .executeShellV2Command(Mockito.startsWith(REMOVE_APP_DATA_COMMAND_PREFIX)); - } - - @Test - public void uninstallPackage_noUpdatePackagePresent_appRemoved() throws Exception { - int numberOfUpdates = 0; - ITestDevice device = createGoodDeviceWithSystemAppInstalled(numberOfUpdates); - - SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device); - - Mockito.verify(device, Mockito.times(numberOfUpdates + 1)) - .uninstallPackage(TEST_PACKAGE_NAME); - Mockito.verify(device, Mockito.times(1)) - .executeShellV2Command(Mockito.startsWith(REMOVE_SYSTEM_APP_COMMAND_PREFIX)); - } - - @Test - public void uninstallPackage_someUpdatePackagesPresent_appRemoved() throws Exception { - int numberOfUpdates = 2; - ITestDevice device = createGoodDeviceWithSystemAppInstalled(numberOfUpdates); - - SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device); - - Mockito.verify(device, Mockito.times(numberOfUpdates + 1)) - .uninstallPackage(TEST_PACKAGE_NAME); - Mockito.verify(device, Mockito.times(1)) - .executeShellV2Command(Mockito.startsWith(REMOVE_SYSTEM_APP_COMMAND_PREFIX)); - } - - @Test - public void uninstallPackage_tooManyUpdatePackagesPresent_throwsException() throws Exception { - ITestDevice device = - createGoodDeviceWithSystemAppInstalled( - SystemPackageUninstaller.MAX_NUMBER_OF_UPDATES + 1); - - assertThrows( - TargetSetupError.class, - () -> SystemPackageUninstaller.uninstallPackage(TEST_PACKAGE_NAME, device)); - } - - private ITestDevice createGoodDeviceWithUserAppInstalled() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - - CommandResult commandResult = createSuccessfulCommandResult(); - commandResult.setStdout( - String.format("package:/data/app/%s/%s.apk", TEST_PACKAGE_NAME, TEST_PACKAGE_NAME)); - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith( - GET_PACKAGE_INSTALL_PATH_COMMAND_PREFIX))) - .thenReturn(commandResult); - - return device; - } - - private ITestDevice createGoodDeviceWithAppNotInstalled() throws Exception { - ITestDevice device = createGoodDeviceWithSystemAppInstalled(); - CommandResult commandResult = createSuccessfulCommandResult(); - commandResult.setStdout(""); - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith( - CHECK_PACKAGE_INSTALLED_COMMAND_PREFIX))) - .thenReturn(commandResult); - return device; - } - - private ITestDevice createGoodDeviceWithSystemAppInstalled() throws Exception { - return createGoodDeviceWithSystemAppInstalled(1); - } - - private ITestDevice createGoodDeviceWithSystemAppInstalled(int numberOfUpdatesInstalled) - throws Exception { - ITestDevice device = Mockito.mock(ITestDevice.class); - CommandResult commandResult; - - // Is framework running - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.eq(SystemPackageUninstaller.PM_CHECK_COMMAND))) - .thenReturn(createSuccessfulCommandResult()); - - // Uninstall updates - String uninstallFailureMessage = "Failure [DELETE_FAILED_INTERNAL_ERROR]"; - if (numberOfUpdatesInstalled == 0) { - Mockito.when(device.uninstallPackage(TEST_PACKAGE_NAME)) - .thenReturn(uninstallFailureMessage); - } else { - String[] uninstallResults = new String[numberOfUpdatesInstalled]; - uninstallResults[numberOfUpdatesInstalled - 1] = uninstallFailureMessage; - Mockito.when(device.uninstallPackage(TEST_PACKAGE_NAME)) - .thenReturn(null, uninstallResults); - } - - // List package - commandResult = createSuccessfulCommandResult(); - commandResult.setStdout("package:" + TEST_PACKAGE_NAME); - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith( - CHECK_PACKAGE_INSTALLED_COMMAND_PREFIX))) - .thenReturn(commandResult); - - // Get package path - commandResult = createSuccessfulCommandResult(); - commandResult.setStdout( - String.format( - "package:%s/%s/%s.apk", - SYSTEM_APP_INSTALL_DIRECTORY, TEST_PACKAGE_NAME, TEST_PACKAGE_NAME)); - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith( - GET_PACKAGE_INSTALL_PATH_COMMAND_PREFIX))) - .thenReturn(commandResult); - - // Adb root - Mockito.when(device.isAdbRoot()).thenReturn(false); - Mockito.when(device.enableAdbRoot()).thenReturn(true); - - // Adb remount - Mockito.doNothing().when(device).remountSystemWritable(); - - // Remove package install directory - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith(REMOVE_SYSTEM_APP_COMMAND_PREFIX))) - .thenReturn(createSuccessfulCommandResult()); - - // Remove package data directory - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith(REMOVE_APP_DATA_COMMAND_PREFIX))) - .thenReturn(createSuccessfulCommandResult()); - - // Restart framework - Mockito.when(device.executeShellV2Command(ArgumentMatchers.eq("start"))) - .thenReturn(createSuccessfulCommandResult()); - Mockito.when(device.executeShellV2Command(ArgumentMatchers.eq("stop"))) - .thenReturn(createSuccessfulCommandResult()); - - // Disable adb root - Mockito.when(device.disableAdbRoot()).thenReturn(true); - - // Remount read only - Mockito.when( - device.executeShellV2Command( - ArgumentMatchers.startsWith(MOUNT_COMMAND_PREFIX))) - .thenReturn(createSuccessfulCommandResult()); - - return device; - } - - private static CommandResult createSuccessfulCommandResult() { - CommandResult commandResult = new CommandResult(CommandStatus.SUCCESS); - commandResult.setExitCode(0); - return commandResult; - } - - private static CommandResult createFailedCommandResult() { - CommandResult commandResult = new CommandResult(CommandStatus.FAILED); - commandResult.setExitCode(1); - return commandResult; - } -} diff --git a/harness/src/test/java/com/android/csuite/testing/Correspondences.java b/harness/src/test/java/com/android/csuite/testing/Correspondences.java deleted file mode 100644 index c388e23..0000000 --- a/harness/src/test/java/com/android/csuite/testing/Correspondences.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.csuite.testing; - -import com.google.common.truth.Correspondence; - -/** - * Useful implementations of {@link Correspondence}. - * - * <p>Correspondences are used with {@code IterableSubject.comparingElementsUsing(Correspondence)} - * and allow for making assertions on elements besides simple equality. See {@link Correspondence} - * for more information. - */ -// TODO(hzalek): Move this into a dedicated testing library. -public final class Correspondences { - - private static final Correspondence<Object, Class<?>> INSTANCE_OF = - Correspondence.from( - (Object obj, Class<?> clazz) -> { - return clazz.isInstance(obj); - }, - "is an instance of"); - - /** - * Returns a {@link Correspondence} that determines whether elements are instances of the class - * they are compared to. - */ - public static Correspondence<Object, Class<?>> instanceOf() { - return INSTANCE_OF; - } - - private Correspondences() {} -} diff --git a/harness/src/test/java/com/android/csuite/testing/CorrespondencesTest.java b/harness/src/test/java/com/android/csuite/testing/CorrespondencesTest.java deleted file mode 100644 index 5a4dbcf..0000000 --- a/harness/src/test/java/com/android/csuite/testing/CorrespondencesTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.csuite.testing; - -import static com.android.csuite.testing.Correspondences.instanceOf; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.common.collect.ImmutableList; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public final class CorrespondencesTest { - - @Test(expected = AssertionError.class) - public void instanceOf_comparedToDifferentClass_fails() { - assertThat(stringList()).comparingElementsUsing(instanceOf()).contains(Integer.class); - } - - @Test - public void instanceOf_comparedToSuperType_succeeds() { - assertThat(stringList()).comparingElementsUsing(instanceOf()).contains(Object.class); - } - - @Test - public void instanceOf_comparedToSameClass_succeeds() { - assertThat(stringList()).comparingElementsUsing(instanceOf()).contains(String.class); - } - - private static ImmutableList<String> stringList() { - return ImmutableList.of("A", "B", "C"); - } -} diff --git a/harness/src/test/java/com/android/csuite/testing/MoreAsserts.java b/harness/src/test/java/com/android/csuite/testing/MoreAsserts.java deleted file mode 100644 index 306dad4..0000000 --- a/harness/src/test/java/com/android/csuite/testing/MoreAsserts.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.csuite.testing; - -import static com.google.common.truth.Truth.assertThat; - -/** An additional set of assertion methods useful for writing tests. */ -// TODO(hzalek): Move this into a dedicated testing library. -public final class MoreAsserts { - - /** - * Facilitates the use of assertThrows from Java 8 by allowing method references to void methods - * that declare checked exceptions and is not meant to be directly implemented. - */ - public interface ThrowingRunnable { - void run() throws Exception; - } - - /** - * Asserts that {@code runnable} throws an exception of type {@code expectedThrowable} when - * executed. If it does, the exception object is returned. Otherwise, if it does not throw an - * exception or does but of the unexpected type, an {@link AssertionError} is thrown. - * - * <p>This is mostly intended as a drop-in replacement of assertThrows that is only available in - * JUnit 4.13 and later. - * - * @param message the identifying message for the {@link AssertionError} - * @param expectedThrowable the expected type of the exception - * @param runnable a function that is expected to throw an exception when executed - * @return the exception thrown by {@code runnable} - */ - public static <T extends Throwable> T assertThrows( - Class<T> expectedClass, ThrowingRunnable runnable) { - try { - runnable.run(); - } catch (Throwable e) { - assertThat(e).isInstanceOf(expectedClass); - return expectedClass.cast(e); - } - throw new AssertionError( - "Did not throw any when expected instance of: " + expectedClass.getName()); - } - - private MoreAsserts() {} -} diff --git a/harness/src/test/java/com/android/csuite/testing/MoreAssertsTest.java b/harness/src/test/java/com/android/csuite/testing/MoreAssertsTest.java deleted file mode 100644 index e0a968f..0000000 --- a/harness/src/test/java/com/android/csuite/testing/MoreAssertsTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.csuite.testing; - -import static com.android.csuite.testing.MoreAsserts.assertThrows; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public final class MoreAssertsTest { - - @Test(expected = AssertionError.class) - public void assertThrows_noExceptionThrown_fails() { - assertThrows( - Throwable.class, - () -> { - /* This comment works around a presubmit hook warning that complains about a - * missing whitespace after the '{' which if added results in another warning - * about the file being badly formatted. */ - }); - } - - @Test(expected = AssertionError.class) - public void assertThrows_differentExceptionTypeThrown_fails() { - assertThrows( - IllegalArgumentException.class, - () -> { - throw new IllegalStateException(); - }); - } - - @Test(expected = AssertionError.class) - public void assertThrows_superTypeOfExpectedExceptionTypeThrown_fails() { - assertThrows( - IllegalArgumentException.class, - () -> { - throw new RuntimeException(); - }); - } - - @Test - public void assertThrows_expectedExceptionTypeThrown_returnsSameObject() { - IllegalArgumentException expected = new IllegalArgumentException(); - - IllegalArgumentException actual = - assertThrows( - IllegalArgumentException.class, - () -> { - throw expected; - }); - - assertThat(actual).isSameInstanceAs(expected); - } - - @Test - public void assertThrows_subTypeOfExpectedExceptionTypeThrown_returnsSameObject() { - IllegalArgumentException expected = new IllegalArgumentException(); - - RuntimeException actual = - assertThrows( - RuntimeException.class, - () -> { - throw expected; - }); - - assertThat(actual).isSameInstanceAs(expected); - } -} diff --git a/instrumentation/launch/Android.bp b/instrumentation/launch/Android.bp index 1b45c1a..22255f6 100644 --- a/instrumentation/launch/Android.bp +++ b/instrumentation/launch/Android.bp @@ -12,18 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -android_test_helper_app { +android_test { name: "csuite-launch-instrumentation", static_libs: ["androidx.test.rules"], // Include all test java files. srcs: ["src/**/*.java"], - // The value of min sdk version chosen here is the oldest sdk version we - // have tested. Lower version numbers should also work. - min_sdk_version: "29", platform_apis: true, manifest: "src/main/AndroidManifest.xml", test_suites: [ diff --git a/instrumentation/launch/src/main/java/com/android/compatibilitytest/AppCompatibility.java b/instrumentation/launch/src/main/java/com/android/compatibilitytest/AppCompatibility.java index 870076c..5918a62 100644 --- a/instrumentation/launch/src/main/java/com/android/compatibilitytest/AppCompatibility.java +++ b/instrumentation/launch/src/main/java/com/android/compatibilitytest/AppCompatibility.java @@ -34,13 +34,10 @@ import android.os.DropBoxManager; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; -import android.view.KeyEvent; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.Preconditions; - import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -54,7 +51,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.IntStream; /** Application Compatibility Test that launches an application and detects crashes. */ @RunWith(AndroidJUnit4.class) @@ -63,14 +59,15 @@ public final class AppCompatibility { private static final String TAG = AppCompatibility.class.getSimpleName(); private static final String PACKAGE_TO_LAUNCH = "package_to_launch"; private static final String APP_LAUNCH_TIMEOUT_MSECS = "app_launch_timeout_ms"; - private static final String ARG_DISMISS_DIALOG = "ARG_DISMISS_DIALOG"; + private static final String WORKSPACE_LAUNCH_TIMEOUT_MSECS = "workspace_launch_timeout_ms"; private static final Set<String> DROPBOX_TAGS = new HashSet<>(); private static final int MAX_CRASH_SNIPPET_LINES = 20; private static final int MAX_NUM_CRASH_SNIPPET = 3; - private static final int DELAY_AFTER_KEYEVENT_MILLIS = 500; // time waiting for app to launch private int mAppLaunchTimeout = 7000; + // time waiting for launcher home screen to show up + private int mWorkspaceLaunchTimeout = 2000; private Context mContext; private ActivityManager mActivityManager; @@ -116,6 +113,10 @@ public final class AppCompatibility { if (appLaunchTimeoutMsecs != null) { mAppLaunchTimeout = Integer.parseInt(appLaunchTimeoutMsecs); } + String workspaceLaunchTimeoutMsecs = mArgs.getString(WORKSPACE_LAUNCH_TIMEOUT_MSECS); + if (workspaceLaunchTimeoutMsecs != null) { + mWorkspaceLaunchTimeout = Integer.parseInt(workspaceLaunchTimeoutMsecs); + } mInstrumentation.getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0); // set activity controller to suppress crash dialogs and collects them by process name @@ -140,59 +141,54 @@ public final class AppCompatibility { @Test public void testAppStability() throws Exception { String packageName = mArgs.getString(PACKAGE_TO_LAUNCH); - Preconditions.checkStringNotEmpty( - packageName, - String.format( - "Missing argument, use %s to specify the package to launch", - PACKAGE_TO_LAUNCH)); - - Log.d(TAG, "Launching app " + packageName); - Intent intent = getLaunchIntentForPackage(packageName); - if (intent == null) { - Log.w(TAG, String.format("Skipping %s; no launch intent", packageName)); - return; - } - long startTime = System.currentTimeMillis(); - launchActivity(packageName, intent); - - if (mArgs.getString(ARG_DISMISS_DIALOG, "false").equals("true")) { - // Attempt to dismiss any dialogs which some apps display to 'gracefully' handle - // errors. The dialog prevents the app from crashing thereby hiding issues. The - // first key event is to select a default button on the error dialog if any while - // the second event pushes the button. - IntStream.range(0, 2) - .forEach(i -> mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ENTER)); - // Give the app process enough time to terminate after dismissing the error. - Thread.sleep(DELAY_AFTER_KEYEVENT_MILLIS); - } - - checkDropbox(startTime, packageName); - if (mAppErrors.containsKey(packageName)) { - StringBuilder message = - new StringBuilder("Error(s) detected for package: ").append(packageName); - List<String> errors = mAppErrors.get(packageName); - for (int i = 0; i < MAX_NUM_CRASH_SNIPPET && i < errors.size(); i++) { - String err = errors.get(i); - message.append("\n\n"); - // limit the size of each crash snippet - message.append(truncate(err, MAX_CRASH_SNIPPET_LINES)); + if (packageName != null) { + Log.d(TAG, "Launching app " + packageName); + Intent intent = getLaunchIntentForPackage(packageName); + if (intent == null) { + Log.w(TAG, String.format("Skipping %s; no launch intent", packageName)); + return; } - if (errors.size() > MAX_NUM_CRASH_SNIPPET) { - message.append( - String.format( - "\n... %d more errors omitted ...", - errors.size() - MAX_NUM_CRASH_SNIPPET)); + long startTime = System.currentTimeMillis(); + launchActivity(packageName, intent); + try { + checkDropbox(startTime, packageName); + if (mAppErrors.containsKey(packageName)) { + StringBuilder message = + new StringBuilder("Error(s) detected for package: ") + .append(packageName); + List<String> errors = mAppErrors.get(packageName); + for (int i = 0; i < MAX_NUM_CRASH_SNIPPET && i < errors.size(); i++) { + String err = errors.get(i); + message.append("\n\n"); + // limit the size of each crash snippet + message.append(truncate(err, MAX_CRASH_SNIPPET_LINES)); + } + if (errors.size() > MAX_NUM_CRASH_SNIPPET) { + message.append( + String.format( + "\n... %d more errors omitted ...", + errors.size() - MAX_NUM_CRASH_SNIPPET)); + } + Assert.fail(message.toString()); + } + // last check: see if app process is still running + Assert.assertTrue( + "app package \"" + + packageName + + "\" no longer found in running " + + "tasks, but no explicit crashes were detected; check logcat for " + + "details", + processStillUp(packageName)); + } finally { + returnHome(); } - Assert.fail(message.toString()); + } else { + Log.d( + TAG, + "Missing argument, use " + + PACKAGE_TO_LAUNCH + + " to specify the package to launch"); } - // last check: see if app process is still running - Assert.assertTrue( - "app package \"" - + packageName - + "\" no longer found in running " - + "tasks, but no explicit crashes were detected; check logcat for " - + "details", - processStillUp(packageName)); } /** @@ -247,6 +243,19 @@ public final class AppCompatibility { } } + private void returnHome() { + Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // Send the "home" intent and wait 2 seconds for us to get there + mContext.startActivity(homeIntent); + try { + Thread.sleep(mWorkspaceLaunchTimeout); + } catch (InterruptedException e) { + // ignore + } + } + private Intent getLaunchIntentForPackage(String packageName) { UiModeManager umm = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE); boolean isLeanback = umm.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; diff --git a/integration_tests/Android.bp b/integration_tests/Android.bp deleted file mode 100644 index 84eaa2e..0000000 --- a/integration_tests/Android.bp +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// The below module creates a standalone zip that end-to-end tests can depend -// on for running the suite. This is a workaround since we can't use csuite.zip -// which is defined in an external Makefile that Soong can't depend on. -// -// Besides listing jars we know the launcher script depends on which is -// brittle, this is a hack for several reasons. First, we're listing our -// dependencies in the tools attribute when we should be using the 'srcs' -// attribute. Second, we're accessing jars using a path relative to a known -// artifact location instead of using the Soong 'location' feature. - -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -java_genrule_host { - name: "csuite_standalone_zip", - cmd: "ANDROID_CSUITE=$(genDir)/android-csuite && " + - "CSUITE_TOOLS=$${ANDROID_CSUITE}/tools && " + - "CSUITE_TESTCASES=$${ANDROID_CSUITE}/testcases && " + - "ANDROID_HOST_OUT=$$(dirname $(location :csuite-tradefed))/.. && " + - "rm -rf $${CSUITE_TOOLS} && mkdir -p $${CSUITE_TOOLS} && " + - "rm -rf $${CSUITE_TESTCASES} && mkdir -p $${CSUITE_TESTCASES} && " + - "cp $(location :csuite-tradefed) $${CSUITE_TOOLS} && " + - "cp $${ANDROID_HOST_OUT}/framework/csuite-tradefed.jar $${CSUITE_TOOLS} && " + - "cp $(location :tradefed) $${CSUITE_TOOLS} && " + - "cp $(location :compatibility-host-util) $${CSUITE_TOOLS} && " + - // We skip copying the csuite-tradefed-tests jar since its location is - // not straight-forward to deduce and not really necessary. - "touch $${CSUITE_TOOLS}/csuite-tradefed-tests.jar && " + - "cp $(location :csuite_generate_module) $${CSUITE_TOOLS} && " + - "cp $(location :csuite-launch-instrumentation) $${CSUITE_TESTCASES} && " + - "chmod a+x $${CSUITE_TOOLS}/csuite-tradefed && " + - "$(location soong_zip) -o $(out) -d -C $(genDir) -D $${ANDROID_CSUITE}", - out: ["csuite-standalone.zip"], - srcs: [ - ":csuite-launch-instrumentation", - ":tradefed", - ":compatibility-host-util", - ], - tools: [ - "soong_zip", - ":csuite-tradefed", - ":csuite_generate_module", - ], -} - -python_library_host { - name: "csuite_test_utils", - srcs: [ - "csuite_test_utils.py", - ], - defaults: [ - "csuite_python_defaults", - ], - java_data: [ - "csuite_standalone_zip", - ], - libs: [ - "csuite_test", - ], -} - -python_test_host { - name: "csuite_cli_test", - srcs: [ - "csuite_cli_test.py", - ], - test_config_template: "csuite_test_template.xml", - test_suites: [ - "general-tests", - ], - libs: [ - "csuite_test_utils", - ], - defaults: [ - "csuite_python_defaults", - ], -} - -python_test_host { - name: "csuite_crash_detection_test", - srcs: [ - "csuite_crash_detection_test.py", - ], - test_config_template: "csuite_test_template.xml", - test_suites: [ - "general-tests", - ], - libs: [ - "csuite_test_utils", - ], - data: [ - ":csuite_crash_on_launch_test_app", - ":csuite_no_crash_test_app", - ], - defaults: [ - "csuite_python_defaults", - ], - test_options: { - unit_test: false, - }, -} diff --git a/integration_tests/TEST_MAPPING b/integration_tests/TEST_MAPPING deleted file mode 100644 index 9d98a72..0000000 --- a/integration_tests/TEST_MAPPING +++ /dev/null @@ -1,10 +0,0 @@ -{ - "postsubmit": [ - { - "name": "csuite_cli_test" - }, - { - "name": "csuite_crash_detection_test" - } - ] -} diff --git a/integration_tests/csuite_cli_test.py b/integration_tests/csuite_cli_test.py deleted file mode 100644 index 84925e2..0000000 --- a/integration_tests/csuite_cli_test.py +++ /dev/null @@ -1,33 +0,0 @@ -# Lint as: python3 -# -# Copyright 2020, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Tests the C-Suite command line interface.""" - -import csuite_test_utils - - -class CSuiteCliTest(csuite_test_utils.TestCase): - - def test_prints_suite_name(self): - with csuite_test_utils.CSuiteHarness() as harness: - completed_process = harness.run_and_wait( - ['run', 'commandAndExit', 'version']) - - self.assertEqual(0, completed_process.returncode) - self.assertIn('App Compatibility Test Suite', completed_process.stdout) - - -if __name__ == '__main__': - csuite_test_utils.main() diff --git a/integration_tests/csuite_crash_detection_test.py b/integration_tests/csuite_crash_detection_test.py deleted file mode 100644 index 9dd8a00..0000000 --- a/integration_tests/csuite_crash_detection_test.py +++ /dev/null @@ -1,104 +0,0 @@ -# Lint as: python3 -# -# Copyright 2020, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Tests C-Suite's crash detection behavior.""" - -import csuite_test_utils - - -class CrashDetectionTest(csuite_test_utils.TestCase): - - def setUp(self): - super(CrashDetectionTest, self).setUp() - self.adb = csuite_test_utils.Adb() - self.repo = csuite_test_utils.PackageRepository() - self.harness = csuite_test_utils.CSuiteHarness() - - def tearDown(self): - super(CrashDetectionTest, self).tearDown() - self.harness.cleanup() - self.repo.cleanup() - - def test_no_crash_test_passes(self): - test_app_package = 'android.csuite.nocrashtestapp' - self.adb.run(['logcat', '-c']) - - completed_process = self.run_test( - test_app_package=test_app_package, - test_app_module='csuite_no_crash_test_app') - - self.expect_regex(completed_process.stdout, r"""PASSED\s*:\s*1""") - self.expect_app_launched(test_app_package) - self.expect_package_not_installed(test_app_package) - - def test_crash_on_launch_test_fails(self): - test_app_package = 'android.csuite.crashonlaunchtestapp' - self.adb.run(['logcat', '-c']) - - completed_process = self.run_test( - test_app_package=test_app_package, - test_app_module='csuite_crash_on_launch_test_app') - - self.expect_regex(completed_process.stdout, r"""FAILED\s*:\s*1""") - self.expect_app_launched(test_app_package) - self.expect_package_not_installed(test_app_package) - - def run_test(self, test_app_package, test_app_module): - """Set up and run the launcher for a given test app.""" - - # We don't check the return code since adb returns non-zero exit code if - # the package does not exist. - self.adb.uninstall(test_app_package, check=False) - self.assert_package_not_installed(test_app_package) - - module_name = self.harness.add_module(test_app_package) - self.repo.add_package_apks( - test_app_package, csuite_test_utils.get_test_app_apks(test_app_module)) - - file_resolver_class = 'com.android.csuite.config.AppRemoteFileResolver' - - return self.harness.run_and_wait([ - '--serial', - csuite_test_utils.get_device_serial(), - 'run', - 'commandAndExit', - 'launch', - '-m', - module_name, - '--enable-module-dynamic-download', - '--dynamic-download-args', - '%s:uri-template=file://%s/{package}' % - (file_resolver_class, self.repo.get_path()) - ]) - - def expect_regex(self, s, regex): - with self.subTest(): - self.assertRegex(s, regex) - - def assert_package_not_installed(self, package_name): - self.assertNotIn(package_name, self.adb.list_packages()) - - def expect_package_not_installed(self, package_name): - with self.subTest(): - self.assert_package_not_installed(package_name) - - def expect_app_launched(self, tag): - logcat_process = self.adb.run(['logcat', '-d', '-v', 'brief', '-s', tag]) - with self.subTest(): - self.assertIn('App launched', logcat_process.stdout) - - -if __name__ == '__main__': - csuite_test_utils.main() diff --git a/integration_tests/csuite_crash_on_launch_test_app/Android.bp b/integration_tests/csuite_crash_on_launch_test_app/Android.bp deleted file mode 100644 index ec0022b..0000000 --- a/integration_tests/csuite_crash_on_launch_test_app/Android.bp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -android_test_helper_app { - name: "csuite_crash_on_launch_test_app", - srcs: [ - "*.java", - ], - sdk_version: "test_current", - min_sdk_version: "29", - target_sdk_version: "29", -} diff --git a/integration_tests/csuite_crash_on_launch_test_app/AndroidManifest.xml b/integration_tests/csuite_crash_on_launch_test_app/AndroidManifest.xml deleted file mode 100755 index 496aefb..0000000 --- a/integration_tests/csuite_crash_on_launch_test_app/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package='android.csuite.crashonlaunchtestapp' > - <application> - <activity android:name="android.csuite.TestAppActivity" > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/integration_tests/csuite_crash_on_launch_test_app/TestAppActivity.java b/integration_tests/csuite_crash_on_launch_test_app/TestAppActivity.java deleted file mode 100644 index 2838a18..0000000 --- a/integration_tests/csuite_crash_on_launch_test_app/TestAppActivity.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.csuite; - -import android.app.Activity; -import android.os.Bundle; -import android.util.Log; - -public final class TestAppActivity extends Activity { - @Override - public void onCreate(Bundle bundle) { - super.onCreate(bundle); - Log.i(getApplicationContext().getPackageName(), "App launched"); - throw new RuntimeException("Expected exception"); - } -} diff --git a/integration_tests/csuite_no_crash_test_app/Android.bp b/integration_tests/csuite_no_crash_test_app/Android.bp deleted file mode 100644 index e9ee60a..0000000 --- a/integration_tests/csuite_no_crash_test_app/Android.bp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -android_test_helper_app { - name: "csuite_no_crash_test_app", - srcs: [ - "*.java", - ], - sdk_version: "test_current", - min_sdk_version: "29", - target_sdk_version: "29", -} diff --git a/integration_tests/csuite_no_crash_test_app/AndroidManifest.xml b/integration_tests/csuite_no_crash_test_app/AndroidManifest.xml deleted file mode 100755 index bd76721..0000000 --- a/integration_tests/csuite_no_crash_test_app/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package='android.csuite.nocrashtestapp' > - <application> - <activity android:name="android.csuite.TestAppActivity" > - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/integration_tests/csuite_no_crash_test_app/TestAppActivity.java b/integration_tests/csuite_no_crash_test_app/TestAppActivity.java deleted file mode 100644 index e61f484..0000000 --- a/integration_tests/csuite_no_crash_test_app/TestAppActivity.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.csuite; - -import android.app.Activity; -import android.os.Bundle; -import android.util.Log; - -public final class TestAppActivity extends Activity { - @Override - public void onCreate(Bundle bundle) { - super.onCreate(bundle); - Log.i(getApplicationContext().getPackageName(), "App launched"); - } -} diff --git a/integration_tests/csuite_test_utils.py b/integration_tests/csuite_test_utils.py deleted file mode 100644 index e72535f..0000000 --- a/integration_tests/csuite_test_utils.py +++ /dev/null @@ -1,283 +0,0 @@ -# Lint as: python3 -# -# Copyright 2020, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Utilities for C-Suite integration tests.""" - -import argparse -import contextlib -import logging -import os -import pathlib -import shlex -import shutil -import stat -import subprocess -import sys -import tempfile -from typing import Sequence, Text -import zipfile -import csuite_test - -# Export symbols to reduce the number of imports tests have to list. -TestCase = csuite_test.TestCase # pylint: disable=invalid-name -get_device_serial = csuite_test.get_device_serial - -# Keep any created temporary directories for debugging test failures. The -# directories do not need explicit removal since they are created using the -# system's temporary-file facility. -_KEEP_TEMP_DIRS = False - - -class CSuiteHarness(contextlib.AbstractContextManager): - """Interface class for interacting with the C-Suite harness. - - WARNING: Explicitly clean up created instances or use as a context manager. - Not doing so will result in a ResourceWarning for the implicit cleanup which - confuses the TradeFed Python test output parser. - """ - - def __init__(self): - self._suite_dir = pathlib.Path(tempfile.mkdtemp(prefix='csuite')) - logging.debug('Created harness directory: %s', self._suite_dir) - - with zipfile.ZipFile(_get_standalone_zip_path(), 'r') as f: - f.extractall(self._suite_dir) - - # Add owner-execute permission on scripts since zip does not preserve them. - self._launcher_binary = self._suite_dir.joinpath( - 'android-csuite/tools/csuite-tradefed') - _add_owner_exec_permission(self._launcher_binary) - - self._generate_module_binary = self._suite_dir.joinpath( - 'android-csuite/tools/csuite_generate_module') - _add_owner_exec_permission(self._generate_module_binary) - - self._testcases_dir = self._suite_dir.joinpath('android-csuite/testcases') - - def __exit__(self, unused_type, unused_value, unused_traceback): - self.cleanup() - - def cleanup(self): - if _KEEP_TEMP_DIRS: - return - shutil.rmtree(self._suite_dir, ignore_errors=True) - - def add_module(self, package_name: Text) -> Text: - """Generates and adds a test module for the provided package.""" - module_name = 'csuite_%s' % package_name - - with tempfile.TemporaryDirectory() as o: - out_dir = pathlib.Path(o) - package_list_path = out_dir.joinpath('packages.list') - - package_list_path.write_text(package_name + '\n') - - flags = ['--package-list', package_list_path, '--root-dir', out_dir] - - _run_command([self._generate_module_binary] + flags) - - out_file_path = self._testcases_dir.joinpath(module_name + '.config') - shutil.copy( - out_dir.joinpath(package_name, 'AndroidTest.xml'), out_file_path) - - return module_name - - def run_and_wait(self, flags: Sequence[Text]) -> subprocess.CompletedProcess: - """Starts the Tradefed launcher and waits for it to complete.""" - - env = os.environ.copy() - - # Unset environment variables that would cause the script to think it's in a - # build tree. - env.pop('ANDROID_BUILD_TOP', None) - env.pop('ANDROID_HOST_OUT', None) - - # Unset environment variables that would cause TradeFed to find test configs - # other than the ones created by the test. - env.pop('ANDROID_HOST_OUT_TESTCASES', None) - env.pop('ANDROID_TARGET_OUT_TESTCASES', None) - - # Unset environment variables that might cause the suite to pick up a - # connected device that wasn't explicitly specified. - env.pop('ANDROID_SERIAL', None) - - # Set the environment variable that TradeFed requires to find test modules. - env['ANDROID_TARGET_OUT_TESTCASES'] = self._testcases_dir - - return _run_command([self._launcher_binary] + flags, env=env) - - -class PackageRepository(contextlib.AbstractContextManager): - """A file-system based APK repository for use in tests. - - WARNING: Explicitly clean up created instances or use as a context manager. - Not doing so will result in a ResourceWarning for the implicit cleanup which - confuses the TradeFed Python test output parser. - """ - - def __init__(self): - self._root_dir = pathlib.Path(tempfile.mkdtemp(prefix='csuite_apk_dir')) - logging.info('Created repository directory: %s', self._root_dir) - - def __exit__(self, unused_type, unused_value, unused_traceback): - self.cleanup() - - def cleanup(self): - if _KEEP_TEMP_DIRS: - return - shutil.rmtree(self._root_dir, ignore_errors=True) - - def get_path(self) -> pathlib.Path: - """Returns the path to the repository's root directory.""" - return self._root_dir - - def add_package_apks(self, package_name: Text, - apk_paths: Sequence[pathlib.Path]): - """Adds the provided package APKs to the repository.""" - apk_dir = self._root_dir.joinpath(package_name) - - # Raises if the directory already exists. - apk_dir.mkdir() - for f in apk_paths: - shutil.copy(f, apk_dir) - - -class Adb: - """Encapsulates adb functionality to simplify usage in tests. - - Most methods in this class raise an exception if they fail to execute. This - behavior can be overridden by using the check parameter. - """ - - def __init__(self, - adb_binary_path: pathlib.Path = None, - device_serial: Text = None): - self._args = [adb_binary_path or 'adb'] - - device_serial = device_serial or get_device_serial() - if device_serial: - self._args.extend(['-s', device_serial]) - - def shell(self, - args: Sequence[Text], - check: bool = None) -> subprocess.CompletedProcess: - """Runs an adb shell command and waits for it to complete. - - Note that the exit code of the returned object corresponds to that of - the adb command and not the command executed in the shell. - - Args: - args: a sequence of program arguments to pass to the shell. - check: whether to raise if the process terminates with a non-zero exit - code. - - Returns: - An object representing a process that has finished and that can be - queried. - """ - return self.run(['shell'] + args, check) - - def run(self, - args: Sequence[Text], - check: bool = None) -> subprocess.CompletedProcess: - """Runs an adb command and waits for it to complete.""" - return _run_command(self._args + args, check=check) - - def uninstall(self, package_name: Text, check: bool = None): - """Uninstalls the specified package.""" - self.run(['uninstall', package_name], check=check) - - def list_packages(self) -> Sequence[Text]: - """Lists packages installed on the device.""" - p = self.shell(['pm', 'list', 'packages']) - return [l.split(':')[1] for l in p.stdout.splitlines()] - - -def _run_command(args, check=True, **kwargs) -> subprocess.CompletedProcess: - """A wrapper for subprocess.run that overrides defaults and adds logging.""" - env = kwargs.get('env', {}) - - # Log the command-line for debugging failed tests. Note that we convert - # tokens to strings for _shlex_join. - env_str = ['env', '-i'] + ['%s=%s' % (k, v) for k, v in env.items()] - args_str = [str(t) for t in args] - - # Override some defaults. Note that 'check' deviates from this pattern to - # avoid getting warnings about using subprocess.run without an explicitly set - # `check` parameter. - kwargs.setdefault('capture_output', True) - kwargs.setdefault('universal_newlines', True) - - logging.debug('Running command: %s', _shlex_join(env_str + args_str)) - - return subprocess.run(args, check=check, **kwargs) - - -def _add_owner_exec_permission(path: pathlib.Path): - path.chmod(path.stat().st_mode | stat.S_IEXEC) - - -def get_test_app_apks(app_module_name: Text) -> Sequence[pathlib.Path]: - """Returns a test app's apk file paths.""" - return [_get_test_file(app_module_name + '.apk')] - - -def _get_standalone_zip_path(): - """Returns the suite standalone zip file's path.""" - return _get_test_file('csuite-standalone.zip') - - -def _get_test_file(name: Text) -> pathlib.Path: - test_dir = _get_test_dir() - test_file = test_dir.joinpath(name) - - if not test_file.exists(): - raise RuntimeError('Unable to find the file `%s` in the test execution dir ' - '`%s`; are you missing a data dependency in the build ' - 'module?' % (name, test_dir)) - - return test_file - - -def _shlex_join(split_command: Sequence[Text]) -> Text: - """Concatenate tokens and return a shell-escaped string.""" - # This is an alternative to shlex.join that doesn't exist in Python versions - # < 3.8. - return ' '.join(shlex.quote(t) for t in split_command) - - -def _get_test_dir() -> pathlib.Path: - return pathlib.Path(__file__).parent - - -def main(): - global _KEEP_TEMP_DIRS - - parser = argparse.ArgumentParser(parents=[csuite_test.create_arg_parser()]) - parser.add_argument( - '--log-level', - choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], - default='WARNING', - help='sets the logging level threshold') - parser.add_argument( - '--keep-temp-dirs', - type=bool, - help='keeps any created temporary directories for debugging failures') - args, unittest_argv = parser.parse_known_args(sys.argv) - - _KEEP_TEMP_DIRS = args.keep_temp_dirs - logging.basicConfig(level=getattr(logging, args.log_level)) - - csuite_test.run_tests(args, unittest_argv) diff --git a/pylib/Android.bp b/pylib/Android.bp deleted file mode 100644 index 319060f..0000000 --- a/pylib/Android.bp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// The below module creates a standalone zip that end-to-end tests can depend -// on for running the suite. This is a workaround since we can't use csuite.zip -// which is defined in an external Makefile that Soong can't depend on. -// -// Besides listing jars we know the launcher script depends on which is -// brittle, this is a hack for several reasons. First, we're listing our -// dependencies in the tools attribute when we should be using the 'srcs' -// attribute. Second, we're accessing jars using a path relative to a known -// artifact location instead of using the Soong 'location' feature. -// -// Normally we would just use java_genrule_host to avoid these hacks but can't -// do that since Soong currently complains when a python_host_test depends on -// that target since, although compatible, the arch variants (x86_64 and -// common) don't exactly match. - -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -python_library_host { - name: "csuite_test", - srcs: [ - "csuite_test.py", - ], - defaults: [ - "csuite_python_defaults", - ], -} diff --git a/pylib/csuite_test.py b/pylib/csuite_test.py deleted file mode 100644 index fa06ce3..0000000 --- a/pylib/csuite_test.py +++ /dev/null @@ -1,107 +0,0 @@ -# Lint as: python3 -# -# Copyright 2020, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""CSuite-specific automated unit test functionality.""" - -import argparse -import sys -from typing import Any, Sequence, Text -import unittest - -# Export the TestCase class to reduce the number of imports tests have to list. -TestCase = unittest.TestCase - -_DEVICE_SERIAL = None - - -def get_device_serial() -> Text: - """Returns the serial of the connected device.""" - if not _DEVICE_SERIAL: - raise RuntimeError( - 'Device serial is unset, did you call main in your test?') - return _DEVICE_SERIAL - - -def create_arg_parser(add_help: bool = False) -> argparse.ArgumentParser: - """Creates a new parser that can handle the default command-line flags. - - The object returned by this function can be used by other modules that want to - add their own command-line flags. The returned parser is intended to be passed - to the 'parents' argument of ArgumentParser and extend the set of default - flags with additional ones. - - Args: - add_help: whether to add an option which simply displays the parser’s help - message; this is typically false when used from other modules that want to - use the returned parser as a parent argument parser. - - Returns: - A new arg parser that can handle the default flags expected by this module. - """ - - # The below flags are passed in by the TF Python test runner. - parser = argparse.ArgumentParser(add_help=add_help) - - parser.add_argument('-s', '--serial', help='the device serial') - parser.add_argument( - '--test-output-file', - help='the file in which to store the test results', - required=True) - - return parser - - -def run_tests(args: Any, unittest_argv: Sequence[Text]) -> None: - """Executes a set of Python unit tests. - - This function is typically used by modules that extend command-line flags. - Callers create their own argument parser with this module's parser as a parent - and parse the command-line. The resulting object is will contain the - attributes expected by this module and is used to call this method. - - Args: - args: an object that contains at least the set of attributes defined in - objects returned when using the default argument parser. - unittest_argv: the list of command-line arguments to forward to - unittest.main. - """ - global _DEVICE_SERIAL - - _DEVICE_SERIAL = args.serial - - with open(args.test_output_file, 'w') as test_output_file: - - # Note that we use a type and not an instance for 'testRunner' since - # TestProgram forwards its constructor arguments when creating an instance - # of the runner type. Not doing so would require us to make sure that the - # parameters passed to TestProgram are aligned with those for creating a - # runner instance. - class TestRunner(unittest.TextTestRunner): - """A test runner that writes test results to the TF-provided file.""" - - def __init__(self, *args, **kwargs): - super(TestRunner, self).__init__( - stream=test_output_file, *args, **kwargs) - - # Setting verbosity is required to generate output that the TradeFed test - # runner can parse. - unittest.TestProgram(verbosity=3, testRunner=TestRunner, argv=unittest_argv) - - -def main(): - """Executes a set of Python unit tests.""" - args, unittest_argv = create_arg_parser(add_help=True).parse_known_args( - sys.argv) - run_tests(args, unittest_argv) diff --git a/pylintrc b/pylintrc deleted file mode 100644 index c8c911f..0000000 --- a/pylintrc +++ /dev/null @@ -1,427 +0,0 @@ -# This Pylint rcfile contains a best-effort configuration to uphold the -# best-practices and style described in the Google Python style guide: -# https://google.github.io/styleguide/pyguide.html -# -# Its canonical open-source location is: -# https://google.github.io/styleguide/pylintrc - -[MASTER] - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=third_party - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Pickle collected data for later comparisons. -persistent=no - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=4 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=apply-builtin, - backtick, - bad-option-value, - basestring-builtin, - buffer-builtin, - c-extension-no-member, - cmp-builtin, - cmp-method, - coerce-builtin, - coerce-method, - delslice-method, - div-method, - duplicate-code, - eq-without-hash, - execfile-builtin, - file-builtin, - filter-builtin-not-iterating, - fixme, - getslice-method, - global-statement, - hex-method, - idiv-method, - implicit-str-concat-in-sequence, - import-error, - import-self, - import-star-module-level, - input-builtin, - intern-builtin, - invalid-str-codec, - locally-disabled, - long-builtin, - long-suffix, - map-builtin-not-iterating, - metaclass-assignment, - next-method-called, - next-method-defined, - no-absolute-import, - no-else-break, - no-else-continue, - no-else-raise, - no-else-return, - no-member, - no-self-use, - nonzero-method, - oct-method, - old-division, - old-ne-operator, - old-octal-literal, - old-raise-syntax, - parameter-unpacking, - print-statement, - raising-string, - range-builtin-not-iterating, - raw_input-builtin, - rdiv-method, - reduce-builtin, - relative-import, - reload-builtin, - round-builtin, - setslice-method, - signature-differs, - standarderror-builtin, - suppressed-message, - sys-max-int, - too-few-public-methods, - too-many-ancestors, - too-many-arguments, - too-many-boolean-expressions, - too-many-branches, - too-many-instance-attributes, - too-many-locals, - too-many-public-methods, - too-many-return-statements, - too-many-statements, - trailing-newlines, - unichr-builtin, - unicode-builtin, - unpacking-in-except, - useless-else-on-loop, - useless-suppression, - using-cmp-argument, - xrange-builtin, - zip-builtin-not-iterating, - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". This option is deprecated -# and it will be removed in Pylint 2.0. -files-output=no - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[BASIC] - -# Good variable names which should always be accepted, separated by a comma -good-names=main,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl - -# Regular expression matching correct function names -function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$ - -# Regular expression matching correct variable names -variable-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct constant names -const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct attribute names -attr-rgx=^_{0,2}[a-z][a-z0-9_]*$ - -# Regular expression matching correct argument names -argument-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=^_?[A-Z][a-zA-Z0-9]*$ - -# Regular expression matching correct module names -module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$ - -# Regular expression matching correct method names -method-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=10 - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=80 - -# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt -# lines made too long by directives to pytype. - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=(?x)( - ^\s*(\#\ )?<?https?://\S+>?$| - ^\s*(from\s+\S+\s+)?import\s+.+$) - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=yes - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check= - -# Maximum number of lines in a module -max-module-lines=99999 - -# String used as indentation unit. The internal Google style guide mandates 2 -# spaces. Google's externaly-published style guide says 4, consistent with -# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google -# projects (like TensorFlow). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=TODO - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_) - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging,absl.logging,tensorflow.google.logging - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub, - TERMIOS, - Bastion, - rexec, - sets - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant, absl - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls, - class_ - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=StandardError, - Exception, - BaseException diff --git a/test_targets/csuite-app-launch/Android.bp b/test_targets/csuite-app-launch/Android.bp deleted file mode 100644 index 256d9f4..0000000 --- a/test_targets/csuite-app-launch/Android.bp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2021 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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -csuite_test { - name: "csuite-app-launch", - test_config_template: "template.xml" -} diff --git a/test_targets/csuite-app-launch/template.xml b/test_targets/csuite-app-launch/template.xml deleted file mode 100644 index 52c4611..0000000 --- a/test_targets/csuite-app-launch/template.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<configuration description="Launches an app and check for crashes"> - <option name="package-name" value="{package}"/> - <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer"> - <option name="test-file-name" value="app://{package}"/> - </target_preparer> - <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/> - <option name="run-command" value="input keyevent KEYCODE_MENU"/> - <option name="run-command" value="input keyevent KEYCODE_HOME"/> - </target_preparer> - <test class="com.android.compatibility.testtype.AppLaunchTest"/> -</configuration>
\ No newline at end of file diff --git a/test_targets/csuite-pre-installed-app-launch/Android.bp b/test_targets/csuite-pre-installed-app-launch/Android.bp deleted file mode 100644 index 539306a..0000000 --- a/test_targets/csuite-pre-installed-app-launch/Android.bp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2021 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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -csuite_test { - name: "csuite-pre-installed-app-launch", - test_config_template: "template.xml" -} diff --git a/test_targets/csuite-pre-installed-app-launch/template.xml b/test_targets/csuite-pre-installed-app-launch/template.xml deleted file mode 100644 index 2d20306..0000000 --- a/test_targets/csuite-pre-installed-app-launch/template.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<configuration description="Launches an app that exists on the device and check for crashes"> - <option name="package-name" value="{package}"/> - <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/> - <option name="run-command" value="input keyevent KEYCODE_MENU"/> - <option name="run-command" value="input keyevent KEYCODE_HOME"/> - </target_preparer> - <test class="com.android.compatibility.testtype.AppLaunchTest"/> -</configuration>
\ No newline at end of file diff --git a/test_targets/csuite-system-app-launch/Android.bp b/test_targets/csuite-system-app-launch/Android.bp deleted file mode 100644 index 2514740..0000000 --- a/test_targets/csuite-system-app-launch/Android.bp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2021 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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -csuite_test { - name: "csuite-system-app-launch", - test_config_template: "template.xml" -} diff --git a/test_targets/csuite-system-app-launch/template.xml b/test_targets/csuite-system-app-launch/template.xml deleted file mode 100644 index 4d1181b..0000000 --- a/test_targets/csuite-system-app-launch/template.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<configuration description="Reinstalls a system app and check for launch crashes."> - <option name="package-name" value="{package}"/> - <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer"> - <option name="test-file-name" value="app://{package}"/> - </target_preparer> - <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/> - <option name="run-command" value="input keyevent KEYCODE_MENU"/> - <option name="run-command" value="input keyevent KEYCODE_HOME"/> - </target_preparer> - <test class="com.android.compatibility.testtype.AppLaunchTest"/> -</configuration>
\ No newline at end of file diff --git a/test_targets/csuite-test-package-launch/Android.bp b/test_targets/csuite-test-package-launch/Android.bp deleted file mode 100644 index 6cdd899..0000000 --- a/test_targets/csuite-test-package-launch/Android.bp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2021 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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -csuite_test { - name: "csuite-test-package-launch", - test_config_template: "template.xml" -} diff --git a/test_targets/csuite-test-package-launch/template.xml b/test_targets/csuite-test-package-launch/template.xml deleted file mode 100644 index 9c97fd3..0000000 --- a/test_targets/csuite-test-package-launch/template.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<configuration description="Installs a test package with -t arg and check for launch crashes"> - <option name="package-name" value="{package}"/> - <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer"> - <option name="test-file-name" value="app://{package}"/> - <option name="install-arg" value="-t"/> - </target_preparer> - <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/> - <option name="run-command" value="input keyevent KEYCODE_MENU"/> - <option name="run-command" value="input keyevent KEYCODE_HOME"/> - </target_preparer> - <test class="com.android.compatibility.testtype.AppLaunchTest"/> -</configuration>
\ No newline at end of file diff --git a/tools/csuite-tradefed/Android.bp b/tools/csuite-tradefed/Android.bp index a441726..82d959e 100644 --- a/tools/csuite-tradefed/Android.bp +++ b/tools/csuite-tradefed/Android.bp @@ -12,10 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - tradefed_binary_host { name: "csuite-tradefed", wrapper: "src/scripts/csuite-tradefed", @@ -37,7 +33,5 @@ java_test_host { "tradefed", "csuite-tradefed", ], - test_options: { - unit_test: true, - }, + test_suites: ["general-tests"], } diff --git a/tools/csuite-tradefed/AndroidTest.xml b/tools/csuite-tradefed/AndroidTest.xml new file mode 100644 index 0000000..850750e --- /dev/null +++ b/tools/csuite-tradefed/AndroidTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="class" value="com.android.compatibility.tradefed.CSuiteTradefedTest" /> + </test> +</configuration> diff --git a/tools/csuite-tradefed/TEST_MAPPING b/tools/csuite-tradefed/TEST_MAPPING new file mode 100644 index 0000000..89c2072 --- /dev/null +++ b/tools/csuite-tradefed/TEST_MAPPING @@ -0,0 +1,8 @@ +{ + "postsubmit": [ + { + "name": "csuite-tradefed-tests", + "host": true + } + ] +} diff --git a/tools/csuite-tradefed/src/scripts/csuite-tradefed b/tools/csuite-tradefed/src/scripts/csuite-tradefed index f3b887a..4277884 100644 --- a/tools/csuite-tradefed/src/scripts/csuite-tradefed +++ b/tools/csuite-tradefed/src/scripts/csuite-tradefed @@ -83,6 +83,7 @@ JAR_DIR=${CSUITE_ROOT}/android-csuite/tools TRADEFED_JAR="tradefed" JARS="tradefed + hosttestlib compatibility-host-util csuite-tradefed csuite-tradefed-tests" @@ -98,7 +99,7 @@ OPTIONAL_JARS=" google-tf-prod-tests" for JAR in $OPTIONAL_JARS; do - if [ -f "${JAR_DIR}/${JAR}.jar" ]; then + if [ -f "$JAR.jar" ]; then JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}.jar fi; done @@ -118,4 +119,4 @@ for j in ${CSUITE_ROOT}/android-csuite/testcases/*.jar; do JAR_PATH=${JAR_PATH}:$j done -java $RDBG_FLAG -cp ${JAR_PATH} -DCSUITE_ROOT=${CSUITE_ROOT} com.android.compatibility.common.tradefed.command.CompatibilityConsole "$@" +java $RDBG_FLAG -cp ${JAR_PATH} -DMTS_ROOT=${CSUITE_ROOT} com.android.compatibility.common.tradefed.command.CompatibilityConsole "$@" diff --git a/tools/csuite_test/Android.bp b/tools/csuite_test/Android.bp deleted file mode 100644 index a1103dc..0000000 --- a/tools/csuite_test/Android.bp +++ /dev/null @@ -1,20 +0,0 @@ -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -bootstrap_go_package { - name: "soong-csuite", - pkgPath: "android/soong/csuite", - deps: [ - "blueprint", - "soong-android", - "soong-java", - ], - srcs: [ - "csuite_test.go", - ], - testSrcs: [ - "csuite_test_test.go", - ], - pluginFor: ["soong_build"], -} diff --git a/tools/csuite_test/csuite_test.go b/tools/csuite_test/csuite_test.go deleted file mode 100644 index 74373f2..0000000 --- a/tools/csuite_test/csuite_test.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2020 Google Inc. All rights reserved. -// -// 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 csuite - -import ( - "android/soong/android" - "android/soong/java" - "strings" -) - -var ( - pctx = android.NewPackageContext("android/soong/csuite") -) - -func init() { - android.RegisterModuleType("csuite_test", CSuiteTestFactory) -} - -type csuiteTestProperties struct { - // Local path to a module template xml file. - // The content of the template will be used to generate test modules at runtime. - Test_config_template *string `android:"path"` - - // Local path to a test plan config xml to be included in the generated plan. - Test_plan_include *string `android:"path"` -} - -type CSuiteTest struct { - // Java TestHost. - java.TestHost - - // C-Suite test properties struct. - csuiteTestProperties csuiteTestProperties -} - -func (cSuiteTest *CSuiteTest) buildCopyConfigTemplateCommand(ctx android.ModuleContext, rule *android.RuleBuilder) string { - if cSuiteTest.csuiteTestProperties.Test_config_template == nil { - ctx.ModuleErrorf(`'test_config_template' is missing.`) - } - inputPath := android.PathForModuleSrc(ctx, *cSuiteTest.csuiteTestProperties.Test_config_template) - genPath := android.PathForModuleGen(ctx, planConfigDirName, ctx.ModuleName()+configTemplateFileExtension) - rule.Command().Textf("cp").Input(inputPath).Output(genPath) - cSuiteTest.AddExtraResource(genPath) - return genPath.Rel() -} - -func (cSuiteTest *CSuiteTest) buildCopyPlanIncludeCommand(ctx android.ModuleContext, rule *android.RuleBuilder) string { - if cSuiteTest.csuiteTestProperties.Test_plan_include == nil { - return emptyPlanIncludePath - } - inputPath := android.PathForModuleSrc(ctx, *cSuiteTest.csuiteTestProperties.Test_plan_include) - genPath := android.PathForModuleGen(ctx, planConfigDirName, "includes", ctx.ModuleName()+".xml") - rule.Command().Textf("cp").Input(inputPath).Output(genPath) - cSuiteTest.AddExtraResource(genPath) - return strings.Replace(genPath.Rel(), "config/", "", -1) -} - -func (cSuiteTest *CSuiteTest) buildWritePlanConfigRule(ctx android.ModuleContext, configTemplatePath string, planIncludePath string) { - planName := ctx.ModuleName() - content := strings.Replace(planTemplate, "{planName}", planName, -1) - content = strings.Replace(content, "{templatePath}", configTemplatePath, -1) - content = strings.Replace(content, "{planInclude}", planIncludePath, -1) - genPath := android.PathForModuleGen(ctx, planConfigDirName, planName+planFileExtension) - android.WriteFileRule(ctx, genPath, content) - cSuiteTest.AddExtraResource(genPath) -} - -func (cSuiteTest *CSuiteTest) GenerateAndroidBuildActions(ctx android.ModuleContext) { - rule := android.NewRuleBuilder(pctx, ctx) - - configTemplatePath := cSuiteTest.buildCopyConfigTemplateCommand(ctx, rule) - planIncludePath := cSuiteTest.buildCopyPlanIncludeCommand(ctx, rule) - cSuiteTest.buildWritePlanConfigRule(ctx, configTemplatePath, planIncludePath) - - rule.Build("CSuite", "generate C-Suite config files") - cSuiteTest.TestHost.GenerateAndroidBuildActions(ctx) -} - -func CSuiteTestFactory() android.Module { - module := &CSuiteTest{} - module.AddProperties(&module.csuiteTestProperties) - installable := true - autoGenConfig := false - java.InitTestHost(&module.TestHost, &installable, []string{"csuite"}, &autoGenConfig) - - java.InitJavaModuleMultiTargets(module, android.HostSupported) - - return module -} - -const ( - emptyPlanIncludePath = `empty` - planConfigDirName = `config` - configTemplateFileExtension = `.xml.template` - planFileExtension = `.xml` - planTemplate = `<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<configuration> - <test class="com.android.csuite.config.ModuleGenerator"> - <option name="template" value="{templatePath}" /> - </test> - <include name="csuite-base" /> - <include name="{planInclude}" /> - <option name="plan" value="{planName}" /> -</configuration> -` -) diff --git a/tools/csuite_test/csuite_test_test.go b/tools/csuite_test/csuite_test_test.go deleted file mode 100644 index daf07b0..0000000 --- a/tools/csuite_test/csuite_test_test.go +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2020 Google Inc. All rights reserved. -// -// 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 csuite - -import ( - "android/soong/android" - "io/ioutil" - "os" - "strings" - "testing" -) - -var buildDir string - -func TestBpContainsTestHostPropsThrowsError(t *testing.T) { - ctx, _ := createContextAndConfig(t, ` - csuite_test { - name: "plan_name", - test_config_template: "test_config.xml.template", - data_native_bins: "bin" - } - `) - - _, errs := ctx.ParseBlueprintsFiles("Android.bp") - - android.FailIfNoMatchingErrors(t, `unrecognized property`, errs) -} - -func TestBpContainsManifestThrowsError(t *testing.T) { - ctx, _ := createContextAndConfig(t, ` - csuite_test { - name: "plan_name", - test_config_template: "test_config.xml.template", - test_config: "AndroidTest.xml" - } - `) - - _, errs := ctx.ParseBlueprintsFiles("Android.bp") - - android.FailIfNoMatchingErrors(t, `unrecognized property`, errs) -} - -func TestBpMissingNameThrowsError(t *testing.T) { - ctx, _ := createContextAndConfig(t, ` - csuite_test { - test_config_template: "test_config.xml.template" - } - `) - - _, errs := ctx.ParseBlueprintsFiles("Android.bp") - - android.FailIfNoMatchingErrors(t, `'name' is missing`, errs) -} - -func TestBpMissingTemplatePathThrowsError(t *testing.T) { - ctx, config := createContextAndConfig(t, ` - csuite_test { - name: "plan_name", - } - `) - - ctx.ParseBlueprintsFiles("Android.bp") - _, errs := ctx.PrepareBuildActions(config) - - android.FailIfNoMatchingErrors(t, `'test_config_template' is missing`, errs) -} - -func TestValidBpMissingPlanIncludeDoesNotThrowError(t *testing.T) { - ctx, config := createContextAndConfig(t, ` - csuite_test { - name: "plan_name", - test_config_template: "test_config.xml.template" - } - `) - - parseBpAndBuild(t, ctx, config) -} - -func TestValidBpMissingPlanIncludeGeneratesPlanXmlWithoutPlaceholders(t *testing.T) { - ctx, config := createContextAndConfig(t, ` - csuite_test { - name: "plan_name", - test_config_template: "test_config.xml.template" - } - `) - - parseBpAndBuild(t, ctx, config) - - module := ctx.ModuleForTests("plan_name", android.BuildOs.String()+"_common") - content := android.ContentFromFileRuleForTests(t, module.Output("config/plan_name.xml")) - if strings.Contains(content, "{") || strings.Contains(content, "}") { - t.Errorf("The generated plan name contains a placeholder: %s", content) - } -} - -func TestGeneratedTestPlanContainsPlanName(t *testing.T) { - ctx, config := createContextAndConfig(t, ` - csuite_test { - name: "plan_name", - test_config_template: "test_config.xml.template" - } - `) - - parseBpAndBuild(t, ctx, config) - - module := ctx.ModuleForTests("plan_name", android.BuildOs.String()+"_common") - content := android.ContentFromFileRuleForTests(t, module.Output("config/plan_name.xml")) - if !strings.Contains(content, "plan_name") { - t.Errorf("The plan name is missing from the generated plan: %s", content) - } -} - -func TestGeneratedTestPlanContainsTemplatePath(t *testing.T) { - ctx, config := createContextAndConfig(t, ` - csuite_test { - name: "plan_name", - test_config_template: "test_config.xml.template" - } - `) - - parseBpAndBuild(t, ctx, config) - - module := ctx.ModuleForTests("plan_name", android.BuildOs.String()+"_common") - content := android.ContentFromFileRuleForTests(t, module.Output("config/plan_name.xml")) - if !strings.Contains(content, "config/plan_name.xml.template") { - t.Errorf("The template path is missing from the generated plan: %s", content) - } -} - -func TestTemplateFileCopyRuleExists(t *testing.T) { - ctx, config := createContextAndConfig(t, ` - csuite_test { - name: "plan_name", - test_config_template: "test_config.xml.template" - } - `) - - parseBpAndBuild(t, ctx, config) - - params := ctx.ModuleForTests("plan_name", android.BuildOs.String()+"_common").Rule("CSuite") - assertFileCopyRuleExists(t, params, "test_config.xml.template", "config/plan_name.xml.template") -} - -func TestGeneratedTestPlanContainsPlanInclude(t *testing.T) { - ctx, config := createContextAndConfig(t, ` - csuite_test { - name: "plan_name", - test_config_template: "test_config.xml.template", - test_plan_include: "include.xml" - } - `) - - parseBpAndBuild(t, ctx, config) - - module := ctx.ModuleForTests("plan_name", android.BuildOs.String()+"_common") - content := android.ContentFromFileRuleForTests(t, module.Output("config/plan_name.xml")) - if !strings.Contains(content, `"includes/plan_name.xml"`) { - t.Errorf("The plan include path is missing from the generated plan: %s", content) - } -} - -func TestPlanIncludeFileCopyRuleExists(t *testing.T) { - ctx, config := createContextAndConfig(t, ` - csuite_test { - name: "plan_name", - test_config_template: "test_config.xml.template", - test_plan_include: "include.xml" - } - `) - - parseBpAndBuild(t, ctx, config) - - params := ctx.ModuleForTests("plan_name", android.BuildOs.String()+"_common").Rule("CSuite") - assertFileCopyRuleExists(t, params, "include.xml", "config/includes/plan_name.xml") -} - -func TestMain(m *testing.M) { - run := func() int { - setUp() - defer tearDown() - - return m.Run() - } - - os.Exit(run()) -} - -func parseBpAndBuild(t *testing.T, ctx *android.TestContext, config android.Config) { - _, parsingErrs := ctx.ParseBlueprintsFiles("Android.bp") - _, buildErrs := ctx.PrepareBuildActions(config) - - android.FailIfErrored(t, parsingErrs) - android.FailIfErrored(t, buildErrs) -} - -func assertFileCopyRuleExists(t *testing.T, params android.TestingBuildParams, src string, dst string) { - assertPathsContains(t, getAllInputPaths(params), src) - assertWritablePathsContainsRel(t, getAllOutputPaths(params), dst) - if !strings.HasPrefix(params.RuleParams.Command, "cp") { - t.Errorf("'cp' command is missing.") - } -} - -func assertPathsContains(t *testing.T, paths android.Paths, path string) { - for _, p := range paths { - if p.String() == path { - return - } - } - t.Errorf("Cannot find expected path %s", path) -} - -func assertWritablePathsContainsRel(t *testing.T, paths android.WritablePaths, relPath string) { - for _, path := range paths { - if path.Rel() == relPath { - return - } - } - t.Errorf("Cannot find expected relative path %s", relPath) -} - -func getAllOutputPaths(params android.TestingBuildParams) android.WritablePaths { - var paths []android.WritablePath - if params.Output != nil { - paths = append(paths, params.Output) - } - if params.ImplicitOutput != nil { - paths = append(paths, params.ImplicitOutput) - } - if params.SymlinkOutput != nil { - paths = append(paths, params.SymlinkOutput) - } - paths = append(paths, params.Outputs...) - paths = append(paths, params.ImplicitOutputs...) - paths = append(paths, params.SymlinkOutputs...) - - return paths -} - -func getAllInputPaths(params android.TestingBuildParams) android.Paths { - var paths []android.Path - if params.Input != nil { - paths = append(paths, params.Input) - } - if params.Implicit != nil { - paths = append(paths, params.Implicit) - } - paths = append(paths, params.Inputs...) - paths = append(paths, params.Implicits...) - - return paths -} - -func setUp() { - var err error - buildDir, err = ioutil.TempDir("", "soong_csuite_test") - if err != nil { - panic(err) - } -} - -func tearDown() { - os.RemoveAll(buildDir) -} - -func createContextAndConfig(t *testing.T, bp string) (*android.TestContext, android.Config) { - t.Helper() - config := android.TestArchConfig(buildDir, nil, bp, nil) - ctx := android.NewTestArchContext(config) - ctx.RegisterModuleType("csuite_test", CSuiteTestFactory) - ctx.Register() - - return ctx, config -} diff --git a/tools/csuite_test/go.mod b/tools/csuite_test/go.mod deleted file mode 100644 index a373cd1..0000000 --- a/tools/csuite_test/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module android/soong/csuite - -require ( - android/soong v0.0.0 - github.com/google/blueprint v0.0.0 -) - -replace android/soong v0.0.0 => ../../../../../build/soong - -replace github.com/golang/protobuf v0.0.0 => ../../../../../external/golang-protobuf - -replace github.com/google/blueprint v0.0.0 => ../../../../../build/blueprint - -go 1.13
\ No newline at end of file diff --git a/tools/script/Android.bp b/tools/script/Android.bp deleted file mode 100644 index c1eb990..0000000 --- a/tools/script/Android.bp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -python_binary_host { - name: "csuite_generate_module", - main: "generate_module.py", - srcs: [ - "generate_module.py", - ], - defaults: [ - "csuite_python_defaults", - ], -} - -python_test_host { - name: "generate_module_test", - srcs: [ - "generate_module.py", - "generate_module_test.py", - ], - libs: [ - "csuite_test", - "pyfakefs", - ], - test_config_template: "csuite_test_template.xml", - test_options: { - unit_test: true, - }, - defaults: [ - "csuite_python_defaults", - ], -} diff --git a/tools/script/csuite_test_template.xml b/tools/script/csuite_test_template.xml deleted file mode 100644 index 837716a..0000000 --- a/tools/script/csuite_test_template.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<configuration> - <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest"> - <option name="par-file-name" value="{MODULE}"/> - <option name="inject-serial-option" value="true"/> - <option name="use-test-output-file" value="true"/> - <option name="test-timeout" value="10m"/> - </test> -</configuration> diff --git a/tools/script/generate_module.py b/tools/script/generate_module.py index 5bda01f..30ae7b4 100644 --- a/tools/script/generate_module.py +++ b/tools/script/generate_module.py @@ -1,6 +1,6 @@ -# Lint as: python3 +#!/usr/bin/env python # -# Copyright (C) 2019 The Android Open Source Project +# Copyright (C) 2020 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,237 +13,246 @@ # 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. -"""This script generates C-Suite configuration files for a list of apps.""" +# +# This script generates C-Suite configuration files for a list of apps. import argparse -import contextlib import glob import os -import string import sys +from xml.dom import minidom +from xml.etree import cElementTree as ET +from xml.sax import saxutils -from typing import IO, Set, Text +from typing import IO, List, Text _ANDROID_BP_FILE_NAME = 'Android.bp' _ANDROID_XML_FILE_NAME = 'AndroidTest.xml' -_AUTO_GENERATE_NOTE = 'THIS FILE WAS AUTO-GENERATED. DO NOT EDIT MANUALLY!' - -DEFAULT_BUILD_MODULE_TEMPLATE = string.Template("""\ -// 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. -// ${auto_generate_note} +_TF_TEST_APP_INSTALL_SETUP =\ + 'com.android.tradefed.targetprep.TestAppInstallSetup' +_CSUITE_APP_SETUP_PREPARER =\ + 'com.android.compatibility.targetprep.AppSetupPreparer' +_CSUITE_LAUNCH_TEST_CLASS =\ + 'com.android.compatibility.testtype.AppLaunchTest' -csuite_config { - name: "csuite_${package_name}", -} -""") +_CONFIG_TYPE_TARGET_PREPARER = 'target_preparer' +_CONFIG_TYPE_TEST = 'test' -DEFAULT_TEST_MODULE_TEMPLATE = string.Template("""\ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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 +def generate_all_modules_from_config(package_list_file_path, root_dir): + """Generate multiple test and build modules. - 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. ---> -<!-- ${auto_generate_note}--> - -<configuration description="Tests the compatibility of apps"> - <option key="plan" name="config-descriptor:metadata" value="app-launch"/> - <option name="package-name" value="${package_name}"/> - <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer"> - <option name="test-file-name" value="csuite-launch-instrumentation.apk"/> - <option name="test-file-name" value="app://${package_name}"/> - </target_preparer> - <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/> - <option name="run-command" value="input keyevent KEYCODE_MENU"/> - <option name="run-command" value="input keyevent KEYCODE_HOME"/> - </target_preparer> - <test class="com.android.compatibility.testtype.AppLaunchTest"/> -</configuration> -""") - - -def generate_all_modules_from_config(package_list_file_path, - root_dir, - build_module_template_file_path=None, - test_module_template_file_path=None): - """Generate multiple test and build modules. - - Args: + Args: package_list_file_path: path of a file containing package names. root_dir: root directory that modules will be generated in. - build_module_template_file_path: path of a file containing build module - template. - test_module_template_file_path: path of a file containing test module - template. - """ - build_module_template = DEFAULT_BUILD_MODULE_TEMPLATE - test_module_template = DEFAULT_TEST_MODULE_TEMPLATE - if build_module_template_file_path: - with open(build_module_template_file_path, 'r') as f: - build_module_template = string.Template(f.read()) - if test_module_template_file_path: - with open(test_module_template_file_path, 'r') as f: - test_module_template = string.Template(f.read()) - - remove_existing_package_files(root_dir) - - with open(package_list_file_path) as fp: - for line in parse_package_list(fp): - _generate_module_files(line.strip(), root_dir, build_module_template, - test_module_template) + """ + remove_existing_package_files(root_dir) + + with open(package_list_file_path) as fp: + for line in parse_package_list(fp): + _generate_module_files(line.strip(), root_dir) def remove_existing_package_files(root_dir): - for filename in glob.iglob(root_dir + '/**/AndroidTest.xml'): - if _is_auto_generated(filename): - os.remove(filename) + for filename in glob.iglob(root_dir + '**/AndroidTest.xml'): + if _is_auto_generated(filename): + os.remove(filename) - for filename in glob.iglob(root_dir + '/**/Android.bp'): - if _is_auto_generated(filename): - os.remove(filename) + for filename in glob.iglob(root_dir + '**/Android.bp'): + if _is_auto_generated(filename): + os.remove(filename) - _remove_empty_dirs(root_dir) + _remove_empty_dirs(root_dir) def _is_auto_generated(filename): - with open(filename, 'r') as f: - return _AUTO_GENERATE_NOTE in f.read() + with open(filename, 'r') as f: + return 'auto-generated' in f.read() def _remove_empty_dirs(path): - for filename in os.listdir(path): - file_path = os.path.join(path, filename) - if os.path.isdir(file_path) and not os.listdir(file_path): - os.rmdir(file_path) + for filename in os.listdir(path): + file_path = os.path.join(path, filename) + if os.path.isdir(file_path) and not os.listdir(file_path): + os.rmdir(file_path) -def parse_package_list(package_list_file: IO[bytes]) -> Set[bytes]: - packages = {line.strip() for line in package_list_file.readlines()} - for package in packages: - if package and not package.startswith('#'): - yield package +def parse_package_list(package_list_file: IO[bytes]) -> List[bytes]: + return { + line.strip() for line in package_list_file.readlines() if line.strip()} -def _generate_module_files(package_name, root_dir, build_module_template, - test_module_template): - """Generate test and build modules for a single package. +def _generate_module_files(package_name, root_dir): + """Generate test and build modules for a single package. - Args: + Args: package_name: package name of test and build modules. root_dir: root directory that modules will be generated in. - build_module_template: template for build module. - test_module_template: template for test module. - """ - package_dir = _create_package_dir(root_dir, package_name) + """ + package_dir = _create_package_dir(root_dir, package_name) - build_module_path = os.path.join(package_dir, _ANDROID_BP_FILE_NAME) - test_module_path = os.path.join(package_dir, _ANDROID_XML_FILE_NAME) + build_module_path = os.path.join(package_dir, _ANDROID_BP_FILE_NAME) + test_module_path = os.path.join(package_dir, _ANDROID_XML_FILE_NAME) - with open(build_module_path, 'w') as f: - write_module(build_module_template, package_name, f) + with open(build_module_path, 'w') as f: + write_build_module(package_name, f) - with open(test_module_path, 'w') as f: - write_module(test_module_template, package_name, f) + with open(test_module_path, 'w') as f: + write_test_module(package_name, f) def _create_package_dir(root_dir, package_name): - package_dir_path = os.path.join(root_dir, package_name) - os.mkdir(package_dir_path) + package_dir_path = os.path.join(root_dir, package_name) + os.mkdir(package_dir_path) + + return package_dir_path + + +def write_build_module(package_name: Text, out_file: IO[bytes]) -> Text: + build_module = _BUILD_MODULE_HEADER \ + + _BUILD_MODULE_TEMPLATE.format(package_name=package_name) + out_file.write(build_module) + + +def write_test_module(package_name: Text, out_file: IO[bytes]) -> Text: + configuration = ET.Element('configuration', { + 'description': 'Tests the compatibility of apps' + }) + ET.SubElement( + configuration, 'option', { + 'name': 'config-descriptor:metadata', + 'key': 'plan', + 'value': 'csuite-launch' + } + ) + ET.SubElement( + configuration, 'option', { + 'name': 'package-name', + 'value': package_name + } + ) + test_file_name_option = { + 'name': 'test-file-name', + 'value': 'csuite-launch-instrumentation.apk' + } + _add_element_with_option( + configuration, + _CONFIG_TYPE_TARGET_PREPARER, + _TF_TEST_APP_INSTALL_SETUP, + options=[test_file_name_option] + ) + _add_element_with_option( + configuration, + _CONFIG_TYPE_TARGET_PREPARER, + _CSUITE_APP_SETUP_PREPARER + ) + _add_element_with_option( + configuration, + _CONFIG_TYPE_TEST, + _CSUITE_LAUNCH_TEST_CLASS + ) + + test_module = _TEST_MODULE_HEADER + _prettify(configuration) + out_file.write(test_module) + + +def _add_element_with_option(elem, sub_elem, class_name, options=None): + if options is None: + options = [] + + new_elem = ET.SubElement( + elem, sub_elem, { + 'class': class_name, + } + ) + for option in options: + ET.SubElement( + new_elem, 'option', option + ) + + +def _prettify(elem: ET.Element) -> Text: + declaration = minidom.Document().toxml() + parsed = minidom.parseString(ET.tostring(elem, 'utf-8')) + + return saxutils.unescape( + parsed.toprettyxml(indent=' ')[len(declaration) + 1:]) + +_BUILD_MODULE_HEADER = """// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file was auto-generated by test/app_compat/csuite/tools/script/generate_module.py. +// Do not edit manually. + +""" - return package_dir_path +_BUILD_MODULE_TEMPLATE = """csuite_config {{ + name: "csuite_{package_name}", +}} +""" +_TEST_MODULE_HEADER = """<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at -def write_module(template: string.Template, package_name: Text, - out_file: IO[bytes]) -> Text: - """Writes the build or test module for the provided package into a file.""" - test_module = template.substitute( - package_name=package_name, auto_generate_note=_AUTO_GENERATE_NOTE) - out_file.write(test_module) + 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. +--> +<!-- This file was auto-generated by test/app_compat/csuite/tools/script/generate_module.py. + Do not edit manually. +--> + +""" def _file_path(path): - if os.path.isfile(path): - return path - raise argparse.ArgumentTypeError('%s is not a valid path' % path) + if os.path.isfile(path): + return path + raise argparse.ArgumentTypeError('%s is not a valid path' % path) def _dir_path(path): - if os.path.isdir(path): - return path - raise argparse.ArgumentTypeError('%s is not a valid path' % path) - - -@contextlib.contextmanager -def _redirect_sys_output(out, err): - current_out, current_err = sys.stdout, sys.stderr - try: - sys.stdout, sys.stderr = out, err - yield - finally: - sys.stdout, sys.stderr = current_out, current_err - - -def parse_args(args, out=sys.stdout, err=sys.stderr): - """Parses the provided sequence of arguments.""" - parser = argparse.ArgumentParser() - parser.add_argument( - '--package-list', - type=_file_path, - required=True, - help='path of the file containing package names') - parser.add_argument( - '--root-dir', - type=_dir_path, - required=True, - help='path of the root directory that' + 'modules will be generated in') - parser.add_argument( - '--test-module-template', - type=_file_path, - required=False, - help='path of the file containing test module configuration template') - parser.add_argument( - '--build-module-template', - type=_file_path, - required=False, - help='path of the file containing build module configuration template') - - # We redirect stdout and stderr to improve testability since ArgumentParser - # always writes to those files. More specifically, the TradeFed python test - # runner will choke parsing output that is not in the expected format. - with _redirect_sys_output(out, err): + if os.path.isdir(path): + return path + raise argparse.ArgumentTypeError('%s is not a valid path' % path) + + +def parse_args(args): + parser = argparse.ArgumentParser() + parser.add_argument('--package_list', + type=_file_path, + required=True, + help='path of the file containing package names') + parser.add_argument('--root_dir', + type=_dir_path, + required=True, + help='path of the root directory that' + + 'modules will be generated in') return parser.parse_args(args) def main(): - parser = parse_args(sys.argv[1:]) - generate_all_modules_from_config(parser.package_list, parser.root_dir, - parser.build_module_template, - parser.test_module_template) - + parser = parse_args(sys.argv[1:]) + generate_all_modules_from_config(parser.package_list, parser.root_dir) if __name__ == '__main__': - main() + main() diff --git a/tools/script/generate_module_test.py b/tools/script/generate_module_test.py deleted file mode 100644 index 09370a0..0000000 --- a/tools/script/generate_module_test.py +++ /dev/null @@ -1,245 +0,0 @@ -# Lint as: python3 -# -# Copyright (C) 2020 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Tests for the generate_module package.""" - -import io -import os -from xml.etree import cElementTree as ET -import csuite_test -import generate_module -from pyfakefs import fake_filesystem_unittest - -_AUTO_GENERATE_NOTE = 'THIS FILE WAS AUTO-GENERATED. DO NOT EDIT MANUALLY!' - - -class WriteTestModuleTest(csuite_test.TestCase): - - def test_output_contains_license(self): - out = io.StringIO() - - generate_module.write_module(generate_module.DEFAULT_BUILD_MODULE_TEMPLATE, - 'a.package.name', out) - - self.assertIn('Copyright', out.getvalue()) - self.assertIn('Android Open Source Project', out.getvalue()) - - def test_output_is_valid_xml(self): - out = io.StringIO() - - generate_module.write_module(generate_module.DEFAULT_TEST_MODULE_TEMPLATE, - 'a.package.name', out) - - self.assert_valid_xml(out.getvalue()) - - def test_output_contains_package_name(self): - package_name = 'a.package.name' - out = io.StringIO() - - generate_module.write_module(generate_module.DEFAULT_TEST_MODULE_TEMPLATE, - 'a.package.name', out) - - self.assertIn(package_name, out.getvalue()) - - def assert_valid_xml(self, xml_str: bytes) -> None: - try: - ET.parse(io.BytesIO(xml_str.encode('utf8'))) - except ET.ParseError as e: - self.fail('Input \'%s\' is not a valid XML document: %s' % (xml_str, e)) - - -class WriteBuildModuleTest(csuite_test.TestCase): - - def test_output_contains_license(self): - out = io.StringIO() - - generate_module.write_module(generate_module.DEFAULT_BUILD_MODULE_TEMPLATE, - 'a.package.name', out) - - self.assertIn('Copyright', out.getvalue()) - self.assertIn('Android Open Source Project', out.getvalue()) - - def test_output_is_valid_build_file(self): - package_name = 'a.package.name' - out = io.StringIO() - - generate_module.write_module(generate_module.DEFAULT_BUILD_MODULE_TEMPLATE, - 'a.package.name', out) - - out_str = out.getvalue() - self.assert_braces_balanced(out_str) - self.assertIn('csuite_config', out_str) - self.assertIn(package_name, out_str) - - def assert_braces_balanced(self, generated_str: bytes) -> None: - """Checks whether all braces in the provided string are balanced.""" - count = 0 - - for c in generated_str: - if c == '{': - count += 1 - elif c == '}': - count -= 1 - - if count < 0: - break - - self.assertEqual(count, 0, - 'Braces in \'%s\' are not balanced' % generated_str) - - -class ParsePackageListTest(csuite_test.TestCase): - - def test_accepts_empty_lines(self): - lines = io.StringIO('\n\n\npackage_name\n\n') - - package_list = generate_module.parse_package_list(lines) - - self.assertListEqual(['package_name'], list(package_list)) - - def test_strips_trailing_whitespace(self): - lines = io.StringIO(' package_name ') - - package_list = generate_module.parse_package_list(lines) - - self.assertListEqual(['package_name'], list(package_list)) - - def test_duplicate_package_name(self): - lines = io.StringIO('\n\npackage_name\n\npackage_name\n') - - package_list = generate_module.parse_package_list(lines) - - self.assertListEqual(['package_name'], list(package_list)) - - def test_ignore_comment_lines(self): - lines = io.StringIO('\n# Comments.\npackage_name\n') - - package_list = generate_module.parse_package_list(lines) - - self.assertListEqual(['package_name'], list(package_list)) - - -class ParseArgsTest(fake_filesystem_unittest.TestCase): - - def setUp(self): - super(ParseArgsTest, self).setUp() - self.setUpPyfakefs() - - def test_configuration_file_not_exist(self): - package_list_file_path = '/test/package_list.txt' - root_dir = '/test/modules' - os.makedirs(root_dir) - - with self.assertRaises(SystemExit): - generate_module.parse_args( - ['--package-list', package_list_file_path, '--root-dir', root_dir], - out=io.StringIO(), - err=io.StringIO()) - - def test_module_dir_not_exist(self): - package_list_file_path = '/test/package_list.txt' - package_name1 = 'package_name_1' - package_name2 = 'package_name_2' - self.fs.create_file( - package_list_file_path, contents=(package_name1 + '\n' + package_name2)) - root_dir = '/test/modules' - - with self.assertRaises(SystemExit): - generate_module.parse_args( - ['--package-list', package_list_file_path, '--root-dir', root_dir], - out=io.StringIO(), - err=io.StringIO()) - - def test_test_module_template_file_not_exist(self): - package_list_file_path = '/test/package_list.txt' - package_name1 = 'package_name_1' - package_name2 = 'package_name_2' - self.fs.create_file( - package_list_file_path, contents=(package_name1 + '\n' + package_name2)) - root_dir = '/test/modules' - os.makedirs(root_dir) - template_file_path = '/test/template.txt' - - with self.assertRaises(SystemExit): - generate_module.parse_args([ - '--package-list', package_list_file_path, '--root-dir', root_dir, - '--test', template_file_path - ], - out=io.StringIO(), - err=io.StringIO()) - - def test_build_module_template_file_not_exist(self): - package_list_file_path = '/test/package_list.txt' - package_name1 = 'package_name_1' - package_name2 = 'package_name_2' - self.fs.create_file( - package_list_file_path, contents=(package_name1 + '\n' + package_name2)) - root_dir = '/test/modules' - os.makedirs(root_dir) - template_file_path = '/test/template.txt' - - with self.assertRaises(SystemExit): - generate_module.parse_args([ - '--package-list', package_list_file_path, '--root-dir', root_dir, - '--template', template_file_path - ], - out=io.StringIO(), - err=io.StringIO()) - - -class GenerateAllModulesFromConfigTest(fake_filesystem_unittest.TestCase): - - def setUp(self): - super(GenerateAllModulesFromConfigTest, self).setUp() - self.setUpPyfakefs() - - def test_creates_package_files(self): - package_list_file_path = '/test/package_list.txt' - package_name1 = 'package_name_1' - package_name2 = 'package_name_2' - self.fs.create_file( - package_list_file_path, contents=(package_name1 + '\n' + package_name2)) - root_dir = '/test/modules' - self.fs.create_dir(root_dir) - - generate_module.generate_all_modules_from_config(package_list_file_path, - root_dir) - - self.assertTrue( - os.path.exists(os.path.join(root_dir, package_name1, 'Android.bp'))) - self.assertTrue( - os.path.exists( - os.path.join(root_dir, package_name1, 'AndroidTest.xml'))) - self.assertTrue( - os.path.exists(os.path.join(root_dir, package_name2, 'Android.bp'))) - self.assertTrue( - os.path.exists( - os.path.join(root_dir, package_name2, 'AndroidTest.xml'))) - - def test_removes_all_existing_package_files(self): - root_dir = '/test/' - package_dir = '/test/existing_package/' - self.fs.create_file( - 'test/existing_package/AndroidTest.xml', contents=_AUTO_GENERATE_NOTE) - self.fs.create_file( - 'test/existing_package/Android.bp', contents=_AUTO_GENERATE_NOTE) - - generate_module.remove_existing_package_files(root_dir) - - self.assertFalse(os.path.exists(package_dir)) - - -if __name__ == '__main__': - csuite_test.main() diff --git a/tools/script/generate_module_unittest.py b/tools/script/generate_module_unittest.py new file mode 100644 index 0000000..4d24162 --- /dev/null +++ b/tools/script/generate_module_unittest.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python +# +# Copyright (C) 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import io +import os +import unittest + +from lxml import etree +from pyfakefs import fake_filesystem_unittest + +import generate_module + + +class WriteTestModuleTest(unittest.TestCase): + + def test_xml_is_valid(self): + package_name = 'package_name' + out = io.StringIO() + + generate_module.write_test_module(package_name, out) + + test_module_generated = out.getvalue() + self.assertTrue(self._contains_license(test_module_generated)) + self.assertTrue(self._is_validate_xml(test_module_generated)) + + def _contains_license(self, generated_str: bytes) -> bool: + return 'Copyright' in generated_str and \ + 'Android Open Source Project' in generated_str + + def _is_validate_xml(self, xml_str: bytes) -> bool: + xmlschema_doc = etree.parse( + io.BytesIO('''<?xml version="1.0" encoding="UTF-8" ?> + <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="configuration"> + <xs:complexType> + <xs:sequence> + <xs:element name="option" minOccurs="0" maxOccurs="unbounded"/> + <xs:element name="target_preparer" minOccurs="0" maxOccurs="unbounded"/> + <xs:element name="test" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + <xs:attribute name="description"/> + </xs:complexType> + </xs:element> + </xs:schema> + '''.encode('utf8'))) + xmlschema = etree.XMLSchema(xmlschema_doc) + + xml_doc = etree.parse(io.BytesIO(xml_str.encode('utf8'))) + result = xmlschema.validate(xml_doc) + + return result + + +class WriteBuildModuleTest(unittest.TestCase): + + def test_build_file_is_valid(self): + package_name = 'package_name' + out = io.StringIO() + + generate_module.write_build_module(package_name, out) + + build_module_generated = out.getvalue() + self.assertTrue(self._contains_license(build_module_generated)) + self.assertTrue(self._are_parentheses_balanced(build_module_generated)) + self.assertIn('csuite_config', build_module_generated) + self.assertIn(package_name, build_module_generated) + + def _contains_license(self, generated_str: bytes) -> bool: + return 'Copyright' in generated_str and \ + 'Android Open Source Project' in generated_str + + def _are_parentheses_balanced(self, generated_str: bytes) -> bool: + parenthese_count = 0 + + for elem in generated_str: + if elem == '{': + parenthese_count += 1 + elif elem == '}': + parenthese_count -= 1 + + if parenthese_count < 0: + return False + + return parenthese_count == 0 + + +class ParsePackageListTest(unittest.TestCase): + + def test_accepts_empty_lines(self): + input = io.StringIO('\n\n\npackage_name\n\n') + + package_list = generate_module.parse_package_list(input) + + self.assertEqual(len(package_list), 1) + self.assertIn('package_name', package_list) + self.assertTrue(all(package_list)) + + def test_strips_trailing_whitespace(self): + input = io.StringIO(' package_name ') + + package_list = generate_module.parse_package_list(input) + + self.assertEqual(len(package_list), 1) + self.assertIn('package_name', package_list) + self.assertTrue(all(package_list)) + + def test_duplicate_package_name(self): + input = io.StringIO('\n\npackage_name\n\npackage_name\n') + + package_list = generate_module.parse_package_list(input) + + self.assertEqual(len(package_list), 1) + self.assertIn('package_name', package_list) + self.assertTrue(all(package_list)) + + +class ParseArgsTest(fake_filesystem_unittest.TestCase): + + def setUp(self): + super(ParseArgsTest, self).setUp() + self.setUpPyfakefs() + + def test_configuration_file_not_exist(self): + package_list_file_path = '/test/package_list.txt' + root_dir = '/test/modules' + os.makedirs(root_dir) + + with self.assertRaises(SystemExit): + generate_module.parse_args( + ['--package_list', package_list_file_path, + '--root_dir', root_dir]) + + def test_module_dir_not_exist(self): + package_list_file_path = '/test/package_list.txt' + package_name1 = 'package_name_1' + package_name2 = 'package_name_2' + self.fs.create_file(package_list_file_path, + contents=(package_name1+'\n'+package_name2)) + root_dir = '/test/modules' + + with self.assertRaises(SystemExit): + generate_module.parse_args( + ['--package_list', package_list_file_path, + '--root_dir', root_dir]) + + +class GenerateAllModulesFromConfigTest(fake_filesystem_unittest.TestCase): + + def setUp(self): + super(GenerateAllModulesFromConfigTest, self).setUp() + self.setUpPyfakefs() + + def test_creates_package_files(self): + package_list_file_path = '/test/package_list.txt' + package_name1 = 'package_name_1' + package_name2 = 'package_name_2' + self.fs.create_file(package_list_file_path, + contents=(package_name1+'\n'+package_name2)) + root_dir = '/test/modules' + self.fs.create_dir(root_dir) + + generate_module.generate_all_modules_from_config( + package_list_file_path, root_dir) + + self.assertTrue(os.path.exists( + os.path.join(root_dir, package_name1, 'Android.bp'))) + self.assertTrue(os.path.exists( + os.path.join(root_dir, package_name1, 'AndroidTest.xml'))) + self.assertTrue(os.path.exists( + os.path.join(root_dir, package_name2, 'Android.bp'))) + self.assertTrue(os.path.exists( + os.path.join(root_dir, package_name2, 'AndroidTest.xml'))) + + def test_removes_all_existing_package_files(self): + root_dir = '/test/' + package_dir = '/test/existing_package/' + existing_package_file1 = 'test/existing_package/AndroidTest.xml' + existing_package_file2 = 'test/existing_package/Android.bp' + self.fs.create_file(existing_package_file1, contents='auto-generated') + self.fs.create_file(existing_package_file2, contents='auto-generated') + + generate_module.remove_existing_package_files(root_dir) + + self.assertFalse(os.path.exists(existing_package_file1)) + self.assertFalse(os.path.exists(existing_package_file2)) + self.assertFalse(os.path.exists(package_dir)) + + +if __name__ == '__main__': + unittest.main() |