diff options
200 files changed, 7173 insertions, 3619 deletions
diff --git a/Android.bp b/Android.bp index 4d6f01bbb..fb8090fb3 100644 --- a/Android.bp +++ b/Android.bp @@ -48,6 +48,8 @@ java_defaults { "-Xep:TryFailThrowable:ERROR", "-Xep:UnnecessaryParentheses:ERROR", "-Xep:UseCorrectAssertInTests:ERROR", + "-XepDisableWarningsInGeneratedCode", // Disable warnings in gRPC generated code. + "-XepExcludedPaths:.*/srcjars/.*" ], }, } @@ -77,6 +79,24 @@ java_library_host { ], } +java_genrule_host { + name: "lab-resource-grpc-gen", + srcs: [ + "proto/monitoring/server/lab_resource.proto", + ], + tools: [ + "aprotoc", + "protoc-gen-grpc-java-plugin", + "soong_zip", + ], + arch: "common", + cmd: "$(location aprotoc) -Iexternal/protobuf/src" + + " -Itools/tradefederation/core/proto/monitoring/server" + + " --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-java-plugin) $(in)" + + " --grpc_out=$(genDir) && $(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)", + out: ["tradefed-grpc.srcjar"], +} + python_library_host { name: "tradefed-protos-py", pkg_path: "atest", @@ -133,6 +153,7 @@ java_library_host { srcs: [ "src/**/*.java", "global_configuration/**/*.java", + ":lab-resource-grpc-gen", ], static_libs: [ "tradefed-common-util", @@ -159,6 +180,14 @@ java_library_host { "tradefed-protos", "tradefed-isolation-protos", "tradefed-lite", + "guava", + "guava-testlib", + "grpc-java", + "grpc-java-testing", + "grpc-java-netty-shaded", + "javax-annotation-api-prebuilt-host-jar", + "opencensus-java-api", + "opencensus-java-contrib-grpc-metrics", ], libs: [ "loganalysis", @@ -210,7 +239,6 @@ droiddoc_host { "-werror " + "-package " + "-devsite ", - create_stubs: false, } sh_binary_host { diff --git a/atest/Android.bp b/atest/Android.bp index 53553fd81..a4031734e 100644 --- a/atest/Android.bp +++ b/atest/Android.bp @@ -134,7 +134,6 @@ python_test_host { "atest_proto", ], test_config: "atest_unittests.xml", - test_suites: ["general-tests"], defaults: ["atest_py2_default"], } diff --git a/atest/TEST_MAPPING b/atest/TEST_MAPPING index 32e6a6dac..09a0ffb68 100644 --- a/atest/TEST_MAPPING +++ b/atest/TEST_MAPPING @@ -28,11 +28,11 @@ } ], "presubmit": [ - { - // Host side ATest unittests. - "name": "atest_unittests", - "host": true - }, +// { +// // Host side ATest unittests. +// "name": "atest_unittests", +// "host": true +// }, { // Host side metrics tests. "name": "asuite_metrics_lib_tests", diff --git a/common_util/com/android/tradefed/config/ConfigurationException.java b/common_util/com/android/tradefed/config/ConfigurationException.java index 2af9c01dc..696ee59ca 100644 --- a/common_util/com/android/tradefed/config/ConfigurationException.java +++ b/common_util/com/android/tradefed/config/ConfigurationException.java @@ -15,10 +15,13 @@ */ package com.android.tradefed.config; -/** - * Thrown if configuration could not be loaded. - */ -public class ConfigurationException extends Exception { +import com.android.tradefed.error.HarnessException; +import com.android.tradefed.result.error.ErrorIdentifier; + +import java.lang.StackWalker.Option; + +/** Thrown if configuration could not be loaded. */ +public class ConfigurationException extends HarnessException { private static final long serialVersionUID = 7742154448569011969L; /** @@ -27,7 +30,19 @@ public class ConfigurationException extends Exception { * @param msg a meaningful error message */ public ConfigurationException(String msg) { - super(msg); + super(msg, null); + setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass()); + } + + /** + * Creates a {@link ConfigurationException}. + * + * @param msg a meaningful error message + * @param error The {@link ErrorIdentifier} associated with the exception + */ + public ConfigurationException(String msg, ErrorIdentifier error) { + super(msg, error); + setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass()); } /** @@ -37,7 +52,20 @@ public class ConfigurationException extends Exception { * @param cause the {@link Throwable} that represents the original cause of the error */ public ConfigurationException(String msg, Throwable cause) { - super(msg, cause); + super(msg, cause, null); + setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass()); + } + + /** + * Creates a {@link ConfigurationException}. + * + * @param msg a meaningful error message + * @param cause the {@link Throwable} that represents the original cause of the error + * @param error The {@link ErrorIdentifier} associated with the exception + */ + public ConfigurationException(String msg, Throwable cause, ErrorIdentifier error) { + super(msg, cause, error); + setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass()); } } diff --git a/device_build_interfaces/com/android/tradefed/error/HarnessException.java b/common_util/com/android/tradefed/error/HarnessException.java index f6fc35e10..f6fc35e10 100644 --- a/device_build_interfaces/com/android/tradefed/error/HarnessException.java +++ b/common_util/com/android/tradefed/error/HarnessException.java diff --git a/device_build_interfaces/com/android/tradefed/error/HarnessRuntimeException.java b/common_util/com/android/tradefed/error/HarnessRuntimeException.java index 1da5df9bb..9b88de772 100644 --- a/device_build_interfaces/com/android/tradefed/error/HarnessRuntimeException.java +++ b/common_util/com/android/tradefed/error/HarnessRuntimeException.java @@ -78,4 +78,10 @@ public class HarnessRuntimeException extends RuntimeException implements IHarnes public String getOrigin() { return mOrigin; } + + protected final void setCallerClass(Class<?> clazz) { + if (clazz != null) { + mOrigin = clazz.getCanonicalName(); + } + } } diff --git a/device_build_interfaces/com/android/tradefed/error/IHarnessException.java b/common_util/com/android/tradefed/error/IHarnessException.java index aa9831bb4..aa9831bb4 100644 --- a/device_build_interfaces/com/android/tradefed/error/IHarnessException.java +++ b/common_util/com/android/tradefed/error/IHarnessException.java diff --git a/common_util/com/android/tradefed/result/LogDataType.java b/common_util/com/android/tradefed/result/LogDataType.java index da1eb9eab..0bcb9a80f 100644 --- a/common_util/com/android/tradefed/result/LogDataType.java +++ b/common_util/com/android/tradefed/result/LogDataType.java @@ -32,7 +32,7 @@ public enum LogDataType { JPEG("jpeg", "image/jpeg", true, false), TAR_GZ("tar.gz", "application/gzip", true, false), GZIP("gz", "application/gzip", true, false), - HPROF("hprof", "text/plain", true, false), + HPROF("hprof", "application/octet-stream", true, false), COVERAGE("ec", "text/plain", false, false), // Emma coverage file NATIVE_COVERAGE("zip", "application/zip", true, false), // gcov coverage archive CLANG_COVERAGE("profdata", "text/plain", false, false), // LLVM indexed profile data @@ -60,6 +60,7 @@ public enum LogDataType { ATRACE("atr", "text/plain", true, false), // atrace -z format KERNEL_TRACE("dat", "text/plain", false, false), // raw kernel ftrace buffer DIR("", "text/plain", false, false), + CFG("cfg", "application/octet-stream", false, true), /* Unknown file type */ UNKNOWN("dat", "text/plain", false, false); diff --git a/test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java b/common_util/com/android/tradefed/result/error/DeviceErrorIdentifier.java index 72efb6ad0..483d02b9b 100644 --- a/test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java +++ b/common_util/com/android/tradefed/result/error/DeviceErrorIdentifier.java @@ -30,6 +30,9 @@ public enum DeviceErrorIdentifier implements ErrorIdentifier { SHELL_COMMAND_ERROR(520_100, FailureStatus.DEPENDENCY_ISSUE), DEVICE_UNEXPECTED_RESPONSE(30_101, FailureStatus.DEPENDENCY_ISSUE), + FAIL_PUSH_FILE(30_102, FailureStatus.DEPENDENCY_ISSUE), + FAIL_PULL_FILE(30_103, FailureStatus.DEPENDENCY_ISSUE), + DEVICE_FAILED_TO_RESET(30_104, FailureStatus.DEPENDENCY_ISSUE), INSTRUMENTATION_CRASH(520_200, FailureStatus.SYSTEM_UNDER_TEST_CRASHED), diff --git a/test_result_interfaces/com/android/tradefed/result/error/ErrorIdentifier.java b/common_util/com/android/tradefed/result/error/ErrorIdentifier.java index 4d00bddf3..48e0b40df 100644 --- a/test_result_interfaces/com/android/tradefed/result/error/ErrorIdentifier.java +++ b/common_util/com/android/tradefed/result/error/ErrorIdentifier.java @@ -15,7 +15,6 @@ */ package com.android.tradefed.result.error; -import com.android.tradefed.result.FailureDescription; import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; /** @@ -33,7 +32,7 @@ public interface ErrorIdentifier { /** * The failure status associated with the identifier, this status is expected to align with the - * {@link FailureDescription} one. + * FailureDescription one. */ public FailureStatus status(); } diff --git a/test_result_interfaces/com/android/tradefed/result/error/InfraErrorIdentifier.java b/common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java index a91045724..619b4fbb6 100644 --- a/test_result_interfaces/com/android/tradefed/result/error/InfraErrorIdentifier.java +++ b/common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java @@ -30,20 +30,28 @@ public enum InfraErrorIdentifier implements ErrorIdentifier { CODE_COVERAGE_ERROR(500_004, FailureStatus.INFRA_FAILURE), MODULE_SETUP_RUNTIME_EXCEPTION(500_005, FailureStatus.CUSTOMER_ISSUE), CONFIGURED_ARTIFACT_NOT_FOUND(500_006, FailureStatus.CUSTOMER_ISSUE), + INVOCATION_TIMEOUT(500_007, FailureStatus.TIMED_OUT), + OPTION_CONFIGURATION_ERROR(500_008, FailureStatus.CUSTOMER_ISSUE), + RUNNER_ALLOCATION_ERROR(500_009, FailureStatus.INFRA_FAILURE), + SCHEDULER_ALLOCATION_ERROR(500_010, FailureStatus.CUSTOMER_ISSUE), // 500_501 - 501_000: Build, Artifacts download related errors ARTIFACT_REMOTE_PATH_NULL(500_501, FailureStatus.INFRA_FAILURE), ARTIFACT_UNSUPPORTED_PATH(500_502, FailureStatus.INFRA_FAILURE), - ARTIFACT_DOWNLOAD_ERROR(500_503, FailureStatus.INFRA_FAILURE), + ARTIFACT_DOWNLOAD_ERROR(500_503, FailureStatus.DEPENDENCY_ISSUE), GCS_ERROR(500_504, FailureStatus.DEPENDENCY_ISSUE), // 501_001 - 501_500: environment issues: For example: lab wifi WIFI_FAILED_CONNECT(501_001, FailureStatus.DEPENDENCY_ISSUE), GOOGLE_ACCOUNT_SETUP_FAILED(501_002, FailureStatus.DEPENDENCY_ISSUE), + NO_WIFI(501_003, FailureStatus.DEPENDENCY_ISSUE), // 502_000 - 502_100: Test issues detected by infra EXPECTED_TESTS_MISMATCH(502_000, FailureStatus.TEST_FAILURE), + // 505_000 - 505_250: Acloud errors + NO_ACLOUD_REPORT(505_000, FailureStatus.DEPENDENCY_ISSUE), + UNDETERMINED(510_000, FailureStatus.UNSET); private final long code; diff --git a/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java b/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java new file mode 100644 index 000000000..5f80ba469 --- /dev/null +++ b/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tradefed.result.error; + +import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; + +/** Error identifier from tests and tests runners. */ +public enum TestErrorIdentifier implements ErrorIdentifier { + MODULE_DID_NOT_EXECUTE(530_001, FailureStatus.NOT_EXECUTED); + + private final long code; + private final FailureStatus status; + + TestErrorIdentifier(int code, FailureStatus status) { + this.code = code; + this.status = status; + } + + @Override + public long code() { + return code; + } + + @Override + public FailureStatus status() { + return status; + } +} diff --git a/common_util/com/android/tradefed/util/SparseImageUtil.java b/common_util/com/android/tradefed/util/SparseImageUtil.java new file mode 100644 index 000000000..ce557a588 --- /dev/null +++ b/common_util/com/android/tradefed/util/SparseImageUtil.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.util; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Utility to unsparse sparse images. + * + * <p>This piece of code is adopted from: + * frameworks/base/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java + */ +public class SparseImageUtil { + private static final int SPARSE_IMAGE_MAGIC = 0xED26FF3A; + + /** + * Tests if file is a sparse image. + * + * @param imgFile a {@link File} that is to be tested. + * @return true if imgFile is a sparse image. + */ + public static boolean isSparse(File imgFile) { + if (!imgFile.isFile()) { + return false; + } + try (FileInputStream in = new FileInputStream(imgFile)) { + // Check magic bytes + return readBuffer(in, 4).getInt() == SPARSE_IMAGE_MAGIC; + } catch (IOException e) { + // Return false if failed to read file + return false; + } + } + + /** + * Unsparses a sparse image file. + * + * @param imgFile a {@link File} that is a sparse image. + * @param destFile a {@link File} to write the unsparsed image to. + * @throws IOException if imgFile is not a sparse image. + */ + public static void unsparse(File imgFile, File destFile) throws IOException { + try (FileInputStream in = new FileInputStream(imgFile)) { + SparseInputStream sis = new SparseInputStream(new BufferedInputStream(in)); + if (!sis.isSparse()) { + throw new IOException("Not a sparse image: " + imgFile); + } + FileUtil.writeToFile(sis, destFile); + } + } + + /** Reads exact number of bytes. */ + private static byte[] readFully(InputStream in, int size) throws IOException { + byte[] buf = new byte[size]; + int n = 0; + int off = 0; + int left = size; + while (left > 0) { + n = in.read(buf, off, left); + if (n < 0) { + throw new IOException("Unexpected EOF in readFully()"); + } + off += n; + left -= n; + } + return buf; + } + + /** Helper that wraps result of readFully() in a ByteBuffer for easy consumption. */ + private static ByteBuffer readBuffer(InputStream in, int size) throws IOException { + return ByteBuffer.wrap(readFully(in, size)).order(ByteOrder.LITTLE_ENDIAN); + } + + /** + * SparseInputStream read from upstream and detects the data format. If the upstream is a valid + * sparse data, it will unsparse it on the fly. Otherwise, it just passthrough as is. + */ + private static class SparseInputStream extends InputStream { + private static final int FILE_HDR_SIZE = 28; + private static final int CHUNK_HDR_SIZE = 12; + + /** + * This class represents a chunk in the Android sparse image. + * + * @see system/core/libsparse/sparse_format.h + */ + private static class SparseChunk { + public static final short RAW = (short) 0xCAC1; + public static final short FILL = (short) 0xCAC2; + public static final short DONTCARE = (short) 0xCAC3; + public short mChunkType; + public int mChunkSize; + public int mTotalSize; + public byte[] mFill; + + @Override + public String toString() { + return String.format( + "type: %x, chunk_size: %d, total_size: %d", + mChunkType, mChunkSize, mTotalSize); + } + + public static SparseChunk readChunk(InputStream in) throws IOException { + SparseChunk chunk = new SparseChunk(); + ByteBuffer buf = readBuffer(in, CHUNK_HDR_SIZE); + chunk.mChunkType = buf.getShort(); + /* padding = */ buf.getShort(); + chunk.mChunkSize = buf.getInt(); + chunk.mTotalSize = buf.getInt(); + if (chunk.mChunkType == FILL) { + chunk.mFill = readFully(in, 4); + } + return chunk; + } + } + + private BufferedInputStream mIn; + private boolean mIsSparse; + private long mBlockSize; + private long mTotalBlocks; + private long mTotalChunks; + private SparseChunk mCur; + private long mLeft; + private int mCurChunks; + + public SparseInputStream(BufferedInputStream in) throws IOException { + mIn = in; + in.mark(FILE_HDR_SIZE * 2); + ByteBuffer buf = readBuffer(mIn, FILE_HDR_SIZE); + mIsSparse = (buf.getInt() == SPARSE_IMAGE_MAGIC); + if (!mIsSparse) { + mIn.reset(); + return; + } + int major = buf.getShort(); + int minor = buf.getShort(); + if (major > 0x1 || minor > 0x0) { + throw new IOException("Unsupported sparse version: " + major + "." + minor); + } + if (buf.getShort() != FILE_HDR_SIZE) { + throw new IOException("Illegal file header size"); + } + if (buf.getShort() != CHUNK_HDR_SIZE) { + throw new IOException("Illegal chunk header size"); + } + mBlockSize = buf.getInt(); + if ((mBlockSize & 0x3) != 0) { + throw new IOException("Illegal block size, must be a multiple of 4: " + mBlockSize); + } + mTotalBlocks = buf.getInt(); + mTotalChunks = buf.getInt(); + mLeft = 0; + mCurChunks = 0; + } + + /** + * Check if it needs to open a new chunk. + * + * @return true if it's EOF + */ + private boolean prepareChunk() throws IOException { + if (mCur == null || mLeft <= 0) { + if (++mCurChunks > mTotalChunks) { + return true; + } + mCur = SparseChunk.readChunk(mIn); + mLeft = mCur.mChunkSize * mBlockSize; + } + return mLeft == 0; + } + + @Override + public int read(byte[] buf, int off, int len) throws IOException { + if (!mIsSparse) { + return mIn.read(buf, off, len); + } + if (prepareChunk()) { + return -1; + } + int n = -1; + switch (mCur.mChunkType) { + case SparseChunk.RAW: + n = mIn.read(buf, off, (int) Math.min(mLeft, len)); + mLeft -= n; + break; + case SparseChunk.DONTCARE: + n = (int) Math.min(mLeft, len); + Arrays.fill(buf, off, off + n, (byte) 0); + mLeft -= n; + break; + case SparseChunk.FILL: + // The FILL type is rarely used, so use a simple implementation. + n = super.read(buf, off, len); + break; + default: + throw new IOException("Unsupported Chunk:" + mCur); + } + return n; + } + + @Override + public int read() throws IOException { + if (!mIsSparse) { + return mIn.read(); + } + if (prepareChunk()) { + return -1; + } + int ret = -1; + switch (mCur.mChunkType) { + case SparseChunk.RAW: + ret = mIn.read(); + break; + case SparseChunk.DONTCARE: + ret = 0; + break; + case SparseChunk.FILL: + ret = mCur.mFill[(4 - ((int) mLeft & 0x3)) & 0x3]; + break; + default: + throw new IOException("Unsupported Chunk:" + mCur); + } + mLeft--; + return ret; + } + + /** + * Get the unsparse size + * + * @return -1 if stream doesn't contain sparse image data. + */ + public long getUnsparseSize() { + if (!mIsSparse) { + return -1; + } + return mBlockSize * mTotalBlocks; + } + + public boolean isSparse() { + return mIsSparse; + } + } +} diff --git a/common_util/com/android/tradefed/util/ZipUtil.java b/common_util/com/android/tradefed/util/ZipUtil.java index 31f3ff8c6..a6902ecdb 100644 --- a/common_util/com/android/tradefed/util/ZipUtil.java +++ b/common_util/com/android/tradefed/util/ZipUtil.java @@ -526,6 +526,7 @@ public class ZipUtil { return; } else if (zipEntry.getCompressedSize() == 0) { // The file is empty, just create an empty file. + FileUtil.mkdirsRWX(targetFile.getParentFile()); targetFile.createNewFile(); return; } diff --git a/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java b/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java index fc2687c97..963805778 100644 --- a/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java +++ b/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java @@ -16,7 +16,6 @@ package com.android.tradefed.util.zip; -import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.util.ByteArrayUtil; import com.google.common.annotations.VisibleForTesting; @@ -241,9 +240,6 @@ public final class CentralDirectoryInfo { if (Long.toHexString(mUncompressedSize).equals("ffffffff") || Long.toHexString(mCompressedSize).equals("ffffffff") || Long.toHexString(mLocalHeaderOffset).equals("ffffffff")) { - CLog.i("Values(compressed/uncompressed size, and relative offset of local header)) in " - + "CentralDirectoryInfo for file name: %s reaches the limitation(0xffffffff), " - + "getting the data from extra field.", mFileName); byte[] zip64FieldId = Arrays.copyOfRange( data, startOffset + mFileNameLength + 46, startOffset + mFileNameLength + 48); // There should be a ZIP64 Field ID(0x0001) existing here. diff --git a/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java b/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java index 1e853c80a..4a996f442 100644 --- a/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java +++ b/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java @@ -15,31 +15,29 @@ */ package com.android.tradefed.device; +import com.android.tradefed.error.HarnessRuntimeException; +import com.android.tradefed.result.error.ErrorIdentifier; + +import java.lang.StackWalker.Option; + /** * Thrown when a device action did not results in the expected results. * * <p>For example: 'pm list users' is vastly expected to return the list of users, failure to do so * should be raised as a DeviceRuntimeException since something went very wrong. */ -public class DeviceRuntimeException extends RuntimeException { +public class DeviceRuntimeException extends HarnessRuntimeException { private static final long serialVersionUID = -7928528651742852301L; /** * Creates a {@link DeviceRuntimeException}. * * @param msg a descriptive error message of the error. + * @param errorId The {@link ErrorIdentifier} categorizing the exception. */ - public DeviceRuntimeException(String msg) { - super(msg); - } - - /** - * Creates a {@link DeviceRuntimeException}. - * - * @param t {@link Throwable} that should be wrapped in {@link DeviceRuntimeException}. - */ - public DeviceRuntimeException(Throwable t) { - super(t); + public DeviceRuntimeException(String msg, ErrorIdentifier errorId) { + super(msg, errorId); + setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass()); } /** @@ -47,8 +45,10 @@ public class DeviceRuntimeException extends RuntimeException { * * @param msg a descriptive error message of the error * @param t {@link Throwable} that should be wrapped in {@link DeviceRuntimeException}. + * @param errorId The {@link ErrorIdentifier} categorizing the exception. */ - public DeviceRuntimeException(String msg, Throwable t) { - super(msg, t); + public DeviceRuntimeException(String msg, Throwable t, ErrorIdentifier errorId) { + super(msg, t, errorId); + setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass()); } } diff --git a/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java b/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java index 3e37f20ba..3163b52af 100644 --- a/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java +++ b/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java @@ -244,6 +244,29 @@ public class ContentProviderHandler { return false; } + /** + * Determines if the file or non-empty directory exists on the device. + * + * @param deviceFilePath The absolute file path on device to check for existence. + * @return True if file/directory exists, False otherwise. If directory is empty, it will return + * False as well. + */ + public boolean doesFileExist(String deviceFilePath) throws DeviceNotAvailableException { + String contentUri = createEscapedContentUri(deviceFilePath); + String queryContentCommand = + String.format( + "content query --user %d --uri %s", mDevice.getCurrentUser(), contentUri); + + String listCommandResult = mDevice.executeShellCommand(queryContentCommand); + + if (NO_RESULTS_STRING.equals(listCommandResult.trim())) { + // No file found. + return false; + } + + return true; + } + /** Returns true if {@link CommandStatus} is successful and there is no error message. */ private boolean isSuccessful(CommandResult result) { if (!CommandStatus.SUCCESS.equals(result.getStatus())) { diff --git a/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java b/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java index caa5f1edd..50084c4cb 100644 --- a/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java +++ b/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java @@ -47,7 +47,13 @@ public class InvocationMetricLogger { CF_FETCH_ARTIFACT_TIME("cf_fetch_artifact_time_ms", false), CF_GCE_CREATE_TIME("cf_gce_create_time_ms", false), CF_LAUNCH_CVD_TIME("cf_launch_cvd_time_ms", false), - CF_INSTANCE_COUNT("cf_instance_count", false); + CF_INSTANCE_COUNT("cf_instance_count", false), + CRASH_FAILURES("crash_failures", true), + UNCAUGHT_CRASH_FAILURES("uncaught_crash_failures", true), + TEST_CRASH_FAILURES("test_crash_failures", true), + UNCAUGHT_TEST_CRASH_FAILURES("uncaught_test_crash_failures", true), + DEVICE_RESET_COUNT("device_reset_count", true), + DEVICE_RESET_MODULES("device_reset_modules", true); private final String mKeyName; // Whether or not to add the value when the key is added again. diff --git a/invocation_interfaces/com/android/tradefed/result/TestResult.java b/invocation_interfaces/com/android/tradefed/result/TestResult.java index daee50c26..1b26db68e 100644 --- a/invocation_interfaces/com/android/tradefed/result/TestResult.java +++ b/invocation_interfaces/com/android/tradefed/result/TestResult.java @@ -208,6 +208,7 @@ public class TestResult { int ignored = 0; int incomplete = 0; + TestStatus lastStatus = null; for (TestResult attempt : results) { mergedResult.mProtoMetrics.putAll(attempt.getProtoMetrics()); mergedResult.mMetrics.putAll(attempt.getMetrics()); @@ -238,6 +239,7 @@ public class TestResult { ignored++; break; } + lastStatus = attempt.getStatus(); } switch (strategy) { @@ -258,7 +260,13 @@ public class TestResult { mergedResult.setStatus(TestStatus.INCOMPLETE); } } else { - mergedResult.setStatus(TestStatus.FAILURE); + if (TestStatus.ASSUMPTION_FAILURE.equals(lastStatus)) { + mergedResult.setStatus(TestStatus.ASSUMPTION_FAILURE); + } else if (TestStatus.IGNORED.equals(lastStatus)) { + mergedResult.setStatus(TestStatus.IGNORED); + } else { + mergedResult.setStatus(TestStatus.FAILURE); + } } break; default: diff --git a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java index a3fdc1e9f..3f3520992 100644 --- a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java +++ b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java @@ -307,6 +307,10 @@ public class TestRunResult { updateTestResult(test, TestStatus.ASSUMPTION_FAILURE, FailureDescription.create(trace)); } + public void testAssumptionFailure(TestDescription test, FailureDescription failure) { + updateTestResult(test, TestStatus.ASSUMPTION_FAILURE, failure); + } + public void testIgnored(TestDescription test) { updateTestResult(test, TestStatus.IGNORED, null); } diff --git a/proto/monitoring/server/lab_resource.proto b/proto/monitoring/server/lab_resource.proto new file mode 100644 index 000000000..5397abe62 --- /dev/null +++ b/proto/monitoring/server/lab_resource.proto @@ -0,0 +1,153 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// The protobuf messages for lab host to export metadata and reosurce metrics. +syntax = "proto3"; + +package dual_home_lab.monitoring_agent.resource_monitoring; + +import "google/protobuf/timestamp.proto"; + +option java_package = "com.google.dualhomelab.monitoringagent.resourcemonitoring"; +option java_multiple_files = true; +option java_generic_services = true; + +// A tag-value pair message represents the metric value. +// For example: +// To represent device disk used percentage. +// metric { +// tag: "/data" +// value: 20.5 +// } +message Metric { + // A string tag associates to the value. + string tag = 1; + // A float value represents the resource value. + float value = 2; +} + +// A message that describes the resource and its metrics. +// For example: +// To represent disk space usage values at certain moment. +// resource { +// resource_name: "disk_space" +// resource_instance: "/data" +// timestamp { +// seconds: 1589342214 +// } +// metric: { +// tag: "avail" +// value: 20.5 +// } +// metric: { +// tag: "used" +// value: 18.7 +// } +// metric: { +// tag: "reserved for root" +// value: 16.2 +// } +// } +message Resource { + // A string resource name. ex. "cpu", "memory", "disk_space". + string resource_name = 1; + // A string reperesent the sub resource name. + string resource_instance = 2; + // The Metric describe the host or device resource usages. + repeated Metric metric = 3; + // The collecting timestamp. + google.protobuf.Timestamp timestamp = 4; +} + +// A name-value message to represent the metadata attribute. +// For example: +// To represent the run target. +// attribute { +// name: "run_target" +// value: "atom-userdebug" +// } +// To reperent the pools. +// attribute { +// name: "pool" +// value: "asit" +// } +// attribute { +// name: "pool" +// value: "apct" +// } +message Attribute { + string name = 1; + string value = 2; +} + +// A message that describes the device state and resource usages. +// For example: +// To represent a monitored host +// host { +// identifier: { +// key: "lab_name" +// value: "us-mtv43" +// } +// identifier: { +// key: "host_name" +// value: "foo.bar.com" +// } +// identifier: { +// key: "test_harness" +// value: "tradefed" +// } +// attribute: {... check the attribute example above ...} +// resource: {... check the resource example abobe ...} +// } +// To represent a monitored device +// device { +// identifier: { +// key: "device_serial" +// value: "VVEG-GIDSAN" +// } +// attribute: {... check the attribute example above ...} +// resource: {... check the resource example abobe ...} +// } +message MonitoredEntity { + // The string map that helps identify the monitored entity + map<string, string> identifier = 1; + // The attribute messages that describe the device metadata. + repeated Attribute attribute = 2; + // The resource messages that describe the device state and resource metrics. + repeated Resource resource = 3; +} + +// A message that describe the state and resource usages for a lab host and its +// connected devices. +message LabResource { + MonitoredEntity host = 1; + repeated MonitoredEntity device = 2; +} + +// The request message to query the LabResource. +message LabResourceRequest {} + +// The service which is intend to export device metrics and metadata to the host +// monitoring agent. The host monitoring agent is responsible for +// collecting host/device metrics and exporting the metrics to user defined +// cloud PubSub topics. +service LabResourceService { + // Queries lab resource message. + rpc GetLabResource(LabResourceRequest) returns (LabResource) { + // The http equivalent is curl http://ADDRESS/v1/lab_resource_message + // (Assuming your service is hosted at the given 'ADDRESS') + } +} diff --git a/res/suite/allowed-preparers.txt b/res/suite/allowed-preparers.txt new file mode 100644 index 000000000..48ed998f2 --- /dev/null +++ b/res/suite/allowed-preparers.txt @@ -0,0 +1,4 @@ +com.android.tradefed.targetprep.CrashCollector +com.android.tradefed.targetprep.DeviceCleaner +com.android.tradefed.targetprep.RootTargetPreparer +com.android.tradefed.targetprep.WifiPreparer
\ No newline at end of file diff --git a/src/com/android/tradefed/build/BootstrapBuildProvider.java b/src/com/android/tradefed/build/BootstrapBuildProvider.java index 383a30cb6..7a22a940b 100644 --- a/src/com/android/tradefed/build/BootstrapBuildProvider.java +++ b/src/com/android/tradefed/build/BootstrapBuildProvider.java @@ -90,6 +90,8 @@ public class BootstrapBuildProvider implements IDeviceBuildProvider { + "Can be repeated. For example --extra-file file_key_1=/path/to/file") private Map<String, File> mExtraFiles = new LinkedHashMap<>(); + private boolean mCreatedTestDir = false; + @Override public IBuildInfo getBuild() throws BuildRetrievalError { throw new UnsupportedOperationException("Call getBuild(ITestDevice)"); @@ -97,6 +99,9 @@ public class BootstrapBuildProvider implements IDeviceBuildProvider { @Override public void cleanUp(IBuildInfo info) { + if (mCreatedTestDir) { + FileUtil.recursiveDelete(mTestsDir); + } } @Override @@ -128,9 +133,9 @@ public class BootstrapBuildProvider implements IDeviceBuildProvider { info.setFile("testsdir", mTestsDir, info.getBuildId()); } // Avoid tests dir being null, by creating a temporary dir. - boolean createdTestDir = false; + mCreatedTestDir = false; if (mTestsDir == null) { - createdTestDir = true; + mCreatedTestDir = true; try { mTestsDir = FileUtil.createTempDir( @@ -147,7 +152,7 @@ public class BootstrapBuildProvider implements IDeviceBuildProvider { .put( FilesKey.TESTS_DIRECTORY, mTestsDir, - !createdTestDir /* shouldNotDelete */); + !mCreatedTestDir /* shouldNotDelete */); } return info; } diff --git a/src/com/android/tradefed/build/DependenciesResolver.java b/src/com/android/tradefed/build/DependenciesResolver.java index f55d0982c..7825f39f8 100644 --- a/src/com/android/tradefed/build/DependenciesResolver.java +++ b/src/com/android/tradefed/build/DependenciesResolver.java @@ -88,7 +88,7 @@ public class DependenciesResolver try { mTestsDir = FileUtil.createTempDir( - "bootstrap-test-dir", + "bootstrap-dep-test-dir", CurrentInvocation.getInfo(InvocationInfo.WORK_FOLDER)); } catch (IOException e) { throw new BuildRetrievalError( diff --git a/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java b/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java index 6e3ffbbd8..e50699c20 100644 --- a/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java +++ b/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java @@ -192,7 +192,9 @@ public class ClusterCommandConfigBuilder { envVars.put("TF_WORK_DIR", mWorkDir.getAbsolutePath()); envVars.putAll(mTestEnvironment.getEnvVars()); envVars.putAll(mTestContext.getEnvVars()); + for (String serial : mCommand.getTargetDeviceSerials()) { + serial = ClusterHostUtil.getLocalDeviceSerial(serial); IDeviceConfiguration device = new DeviceConfigurationHolder(String.format("TF_DEVICE_%d", index++)); device.getDeviceRequirements().setSerial(serial); @@ -203,6 +205,11 @@ public class ClusterCommandConfigBuilder { } deviceConfigs.get(0).addSpecificConfig(new ClusterBuildProvider()); config.setDeviceConfigList(deviceConfigs); + // Perform target preparation in parallel with an unlimited timeout + // TODO(b/166455187): Consider making parallel setup options configurable + config.injectOptionValue("parallel-setup", "true"); + config.injectOptionValue("parallel-setup-timeout", "0"); + config.setTest(new ClusterCommandLauncher()); config.setLogSaver(new ClusterLogSaver()); // TODO(b/135636270): return log path to TFC instead of relying on a specific filename diff --git a/src/com/android/tradefed/cluster/ClusterCommandEvent.java b/src/com/android/tradefed/cluster/ClusterCommandEvent.java index 725e48e72..46db15c27 100644 --- a/src/com/android/tradefed/cluster/ClusterCommandEvent.java +++ b/src/com/android/tradefed/cluster/ClusterCommandEvent.java @@ -38,6 +38,7 @@ public class ClusterCommandEvent implements IClusterEvent { public static final String DATA_KEY_PASSED_TEST_COUNT = "passed_test_count"; public static final String DATA_KEY_FAILED_TEST_RUN_COUNT = "failed_test_run_count"; public static final String DATA_KEY_LOST_DEVICE_DETECTED = "device_lost_detected"; + public static final String DATA_KEY_SUBPROCESS_COMMAND_ERROR = "subprocess_command_error"; // Maximum size of an individual data string value. public static final int MAX_DATA_STRING_SIZE = 4095; diff --git a/src/com/android/tradefed/cluster/ClusterCommandLauncher.java b/src/com/android/tradefed/cluster/ClusterCommandLauncher.java index a79a60588..099220257 100644 --- a/src/com/android/tradefed/cluster/ClusterCommandLauncher.java +++ b/src/com/android/tradefed/cluster/ClusterCommandLauncher.java @@ -42,6 +42,7 @@ import com.android.tradefed.util.RunUtil; import com.android.tradefed.util.StreamUtil; import com.android.tradefed.util.StringEscapeUtils; import com.android.tradefed.util.StringUtil; +import com.android.tradefed.util.SubprocessEventHelper.InvocationFailedEventInfo; import com.android.tradefed.util.SubprocessTestResultsParser; import com.android.tradefed.util.SystemUtil; @@ -192,12 +193,26 @@ public class ClusterCommandLauncher javaCommandArgs.toArray(new String[javaCommandArgs.size()])); if (!result.getStatus().equals(CommandStatus.SUCCESS)) { String error = null; + Throwable cause = null; if (result.getStatus().equals(CommandStatus.TIMED_OUT)) { - error = "timeout"; + error = + String.format( + "Command timed out after %sms", + mConfiguration.getCommandOptions().getInvocationTimeout()); } else { - error = FileUtil.readStringFromFile(stderrFile); + error = + String.format( + "Command finished unsuccessfully: status=%s, exit_code=%s", + result.getStatus(), result.getExitCode()); + InvocationFailedEventInfo errorInfo = + subprocessEventParser.getReportedInvocationFailedEventInfo(); + if (errorInfo != null) { + cause = errorInfo.mCause; + } else { + cause = new Throwable(FileUtil.readStringFromFile(stderrFile)); + } } - throw new RuntimeException(String.format("Command failed to run: %s", error)); + throw new SubprocessCommandException(error, cause); } CLog.i("Successfully ran a command"); diff --git a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java index 2e09168fb..075b3e83b 100644 --- a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java +++ b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java @@ -145,6 +145,7 @@ public class ClusterCommandScheduler extends CommandScheduler { private String mSummary; private Set<String> processedSummaries = new HashSet<>(); private String mError; + private String mSubprocessCommandError; private File mWorkDir; private InvocationStatus mInvocationStatus; @@ -261,6 +262,10 @@ public class ClusterCommandScheduler extends CommandScheduler { super.invocationFailed(cause); mError = StreamUtil.getStackTrace(cause); + if (cause instanceof SubprocessCommandException && cause.getCause() != null) { + // The inner exception holds an exception stack trace from a subprocess. + mSubprocessCommandError = cause.getCause().getMessage(); + } } /** {@inheritDoc} */ @@ -272,6 +277,9 @@ public class ClusterCommandScheduler extends CommandScheduler { createEventBuilder() .setType(ClusterCommandEvent.Type.InvocationEnded) .setData(ClusterCommandEvent.DATA_KEY_ERROR, mError) + .setData( + ClusterCommandEvent.DATA_KEY_SUBPROCESS_COMMAND_ERROR, + mSubprocessCommandError) .build(); getClusterClient().getCommandEventUploader().postEvent(event); getClusterClient().getCommandEventUploader().flush(); @@ -318,6 +326,9 @@ public class ClusterCommandScheduler extends CommandScheduler { .setType(ClusterCommandEvent.Type.InvocationCompleted) .setInvocationStatus(mInvocationStatus) .setData(ClusterCommandEvent.DATA_KEY_ERROR, mError) + .setData( + ClusterCommandEvent.DATA_KEY_SUBPROCESS_COMMAND_ERROR, + mSubprocessCommandError) .setData(ClusterCommandEvent.DATA_KEY_SUMMARY, mSummary) .setData( ClusterCommandEvent.DATA_KEY_FETCH_BUILD_TIME_MILLIS, @@ -497,7 +508,6 @@ public class ClusterCommandScheduler extends CommandScheduler { String runTarget = ClusterHostUtil.getRunTarget( device, runTargetFormat, getClusterOptions().getDeviceTag()); - CLog.d("%s is available", runTarget); devices.put(runTarget, device); } return devices; @@ -605,6 +615,9 @@ public class ClusterCommandScheduler extends CommandScheduler { ClusterCommandEvent.createEventBuilder(commandTask) .setHostName(ClusterHostUtil.getHostName()) .setType(ClusterCommandEvent.Type.AllocationFailed) + .setData( + ClusterCommandEvent.DATA_KEY_ERROR, + StreamUtil.getStackTrace(e)) .build()); eventUploader.flush(); } catch (ConfigurationException | IOException | JSONException e) { @@ -640,7 +653,7 @@ public class ClusterCommandScheduler extends CommandScheduler { if (commandTask.getTargetDeviceSerials() != null) { for (String serial : commandTask.getTargetDeviceSerials()) { cmdLine += " --serial "; - cmdLine += serial; + cmdLine += ClusterHostUtil.getLocalDeviceSerial(serial); } } CLog.i("executing cluster command: [%s] %s", commandTask.getTaskId(), cmdLine); diff --git a/src/com/android/tradefed/cluster/ClusterDeviceInfo.java b/src/com/android/tradefed/cluster/ClusterDeviceInfo.java index 23c39eb23..0fcf3460c 100644 --- a/src/com/android/tradefed/cluster/ClusterDeviceInfo.java +++ b/src/com/android/tradefed/cluster/ClusterDeviceInfo.java @@ -82,7 +82,7 @@ public class ClusterDeviceInfo { */ public JSONObject toJSON() throws JSONException { final JSONObject json = new JSONObject(); - json.put("device_serial", mDeviceDescriptor.getSerial()); + json.put("device_serial", ClusterHostUtil.getUniqueDeviceSerial(mDeviceDescriptor)); json.put("run_target", mRunTarget); json.put("build_id", mDeviceDescriptor.getBuildId()); json.put("product", mDeviceDescriptor.getProduct()); diff --git a/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java b/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java index 33175d457..c8f0fa598 100644 --- a/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java +++ b/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java @@ -108,7 +108,12 @@ public class ClusterDeviceMonitor implements IDeviceMonitor { .setHostName(ClusterHostUtil.getHostName()) .setTfVersion(ClusterHostUtil.getTfVersion()) .setData(getAdditionalHostInfo()) - .setData("host_ip", ClusterHostUtil.getHostIpAddress()) + .setData( + ClusterHostEvent.HOST_IP_KEY, + ClusterHostUtil.getHostIpAddress()) + .setData( + ClusterHostEvent.LABEL_KEY, + String.join(",", getClusterOptions().getLabels())) .setClusterId(getClusterOptions().getClusterId()) .setNextClusterIds(getClusterOptions().getNextClusterIds()) .setLabName(getClusterOptions().getLabName()); diff --git a/src/com/android/tradefed/cluster/ClusterHostEvent.java b/src/com/android/tradefed/cluster/ClusterHostEvent.java index 8a56e93dd..b3bb4304d 100644 --- a/src/com/android/tradefed/cluster/ClusterHostEvent.java +++ b/src/com/android/tradefed/cluster/ClusterHostEvent.java @@ -37,6 +37,8 @@ public class ClusterHostEvent implements IClusterEvent { private Map<String, String> mData = new HashMap<>(); private String mLabName; public static final String EVENT_QUEUE = "host-event-queue"; + public static final String LABEL_KEY = "label"; + public static final String HOST_IP_KEY = "host_ip"; /** Enums of the different types of host events. */ public enum HostEventType { diff --git a/src/com/android/tradefed/cluster/ClusterHostUtil.java b/src/com/android/tradefed/cluster/ClusterHostUtil.java index 0e167b910..5bc620936 100644 --- a/src/com/android/tradefed/cluster/ClusterHostUtil.java +++ b/src/com/android/tradefed/cluster/ClusterHostUtil.java @@ -27,13 +27,22 @@ import com.google.common.net.HostAndPort; import com.google.common.net.InetAddresses; import com.google.common.primitives.Longs; +import java.net.Inet6Address; import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; import java.net.UnknownHostException; import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; + /** Static util functions for TF Cluster to get global config instances, host information, etc. */ public class ClusterHostUtil { @@ -42,39 +51,113 @@ public class ClusterHostUtil { private static String sHostIpAddress = null; static final String DEFAULT_TF_VERSION = "(unknown)"; + static final String EMULATOR_SERIAL_PREFIX = "emulator-"; + static final String NULL_DEVICE_SERIAL_PLACEHOLDER = "(no device serial)"; + static final String UNKNOWN = "UNKNOWN"; private static long sTfStartTime = getCurrentTimeMillis(); /** * Gets the hostname. * + * <p>1. Try to get hostname from InetAddress. 2. If fail, try to get hostname from HOSTNAME + * env. 3. If not set, generate a unique hostname. + * * @return the hostname or null if we were unable to fetch it. */ public static String getHostName() { - if (sHostName == null) { - try { - sHostName = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - CLog.w("failed to get hostname: %s", e); - } + if (sHostName != null) { + return sHostName; + } + try { + sHostName = InetAddress.getLocalHost().getHostName(); + return sHostName; + } catch (UnknownHostException e) { + CLog.w("Failed to get hostname from InetAddress: %s", e); + } + CLog.i("Get hostname from HOSTNAME env."); + sHostName = System.getenv("HOSTNAME"); + if (!Strings.isNullOrEmpty(sHostName)) { + return sHostName; } + sHostName = "unknown-" + UUID.randomUUID().toString(); + CLog.i("No HOSTNAME env set. Generate hostname: %s.", sHostName); return sHostName; } /** + * Returns a unique device serial for a device. + * + * <p>Non-physical devices (e.g. emulator) have pseudo serials which are not unique across + * hosts. This method prefixes those with a hostname to make them unique. + * + * @param device a device descriptor. + * @return a unique device serial. + */ + public static String getUniqueDeviceSerial(DeviceDescriptor device) { + String serial = device.getSerial(); + if (Strings.isNullOrEmpty(serial) + || device.isStubDevice() + || serial.startsWith(EMULATOR_SERIAL_PREFIX)) { + if (Strings.isNullOrEmpty(serial)) { + serial = NULL_DEVICE_SERIAL_PLACEHOLDER; + } + serial = String.format("%s:%s", getHostName(), serial); + } + return serial; + } + + /** + * Returns a local device serial for a given unique device serial. + * + * <p>TFC sends down unique device serials for non-physical devices which TF does not + * understand. This method converts them back to local device serials. + * + * @param serial a unique device serial from TFC. + * @return a local device serial. + */ + public static String getLocalDeviceSerial(String serial) { + String prefix = getHostName() + ":"; + if (serial.startsWith(prefix)) { + return serial.substring(prefix.length()); + } + return serial; + } + /** * Gets the IP address. * - * @return the IP address or null if we were unable to fetch it. + * @return the IPV4 address String or "UNKNOWN" if we were unable to fetch it. */ public static String getHostIpAddress() { if (sHostIpAddress == null) { + List<InetAddress> addresses = new ArrayList<>(); try { - sHostIpAddress = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException e) { - CLog.w("failed to get hostname: %s", e); + Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); + if (interfaces == null) { + return UNKNOWN; + } + for (NetworkInterface networkInterface : Collections.list(interfaces)) { + if (!networkInterface.isUp() || networkInterface.isLoopback()) { + continue; + } + for (InetAddress address : + Collections.list(networkInterface.getInetAddresses())) { + if (address.isLinkLocalAddress() + || address.isLoopbackAddress() + || address instanceof Inet6Address) { + continue; + } + addresses.add(address); + } + } + } catch (SocketException e) { + CLog.w(e); + } + if (!addresses.isEmpty()) { + sHostIpAddress = addresses.get(0).getHostAddress(); } } - return sHostIpAddress; + return sHostIpAddress == null ? UNKNOWN : sHostIpAddress; } /** @@ -143,7 +226,7 @@ public class ClusterHostUtil { txt = device.getDeviceClass(); break; case "SERIAL": - txt = device.getSerial(); + txt = getUniqueDeviceSerial(device); break; case "TAG": if (deviceTags == null || deviceTags.isEmpty()) { diff --git a/src/com/android/tradefed/cluster/SubprocessCommandException.java b/src/com/android/tradefed/cluster/SubprocessCommandException.java new file mode 100644 index 000000000..3be5dc980 --- /dev/null +++ b/src/com/android/tradefed/cluster/SubprocessCommandException.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tradefed.cluster; + +/** A subprocess command failed to run. */ +public class SubprocessCommandException extends RuntimeException { + + public SubprocessCommandException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java b/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java index a69dc8645..6952542fb 100644 --- a/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java +++ b/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java @@ -119,8 +119,12 @@ public class SubprocessConfigBuilder { in = loader.getResourceAsStream(String.format("config/%s", mOriginalConfig)); } if (in == null) { + File f = new File(mOriginalConfig); + if (!f.isAbsolute()) { + f = new File(mWorkDir, mOriginalConfig); + } try { - in = new FileInputStream(mOriginalConfig); + in = new FileInputStream(f); } catch (FileNotFoundException e) { throw new RuntimeException( String.format("Could not find configuration '%s'", mOriginalConfig)); @@ -143,7 +147,14 @@ public class SubprocessConfigBuilder { root.appendChild(reporter); } - File f = new File(mWorkDir, createConfigName(mOriginalConfig)); + File f = new File(mWorkDir, mOriginalConfig); + if (!f.exists() || !f.isFile()) { + // If the original config is an existing file, we need to update it since some old TFs + // check the file system first before bundled configs when loading configs. + // If the original config is not an existing file, we can use any name since the + // original config name will be assigned when creating a injection jar. + f = File.createTempFile("subprocess_config_", ".xml", mWorkDir); + } TransformerFactory transformerFactory = TransformerFactory.newInstance(); try { Transformer transformer = transformerFactory.newTransformer(); diff --git a/src/com/android/tradefed/cluster/SubprocessReportingHelper.java b/src/com/android/tradefed/cluster/SubprocessReportingHelper.java index e65722655..494485948 100644 --- a/src/com/android/tradefed/cluster/SubprocessReportingHelper.java +++ b/src/com/android/tradefed/cluster/SubprocessReportingHelper.java @@ -42,7 +42,8 @@ public class SubprocessReportingHelper { private static final String REPORTER_JAR_NAME = "subprocess-results-reporter.jar"; private static final String CLASS_FILTER = String.format( - "(^%s|^%s|^%s|^%s|^%s).*class$", + "(^%s|^%s|^%s|^%s|^%s|^%s).*class$", + "ErrorIdentifier", "LegacySubprocessResultsReporter", "SubprocessTestResultsParser", "SubprocessEventHelper", diff --git a/src/com/android/tradefed/command/CommandOptions.java b/src/com/android/tradefed/command/CommandOptions.java index 1760e9cf8..b3c5ed366 100644 --- a/src/com/android/tradefed/command/CommandOptions.java +++ b/src/com/android/tradefed/command/CommandOptions.java @@ -23,6 +23,7 @@ import com.android.tradefed.device.metric.AutoLogCollector; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.util.UniqueMultiMap; +import java.time.Duration; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -108,6 +109,10 @@ public class CommandOptions implements ICommandOptions { "[0, shard-count)") private Integer mShardIndex; + @Option(name = "optimize-mainline-test", description = + "Whether or not to optimize the list of test modules for mainline.") + private boolean mOptimizeMainlineTest; + @Option( name = "enable-token-sharding", description = "Whether or not to allow sharding with the token support enabled." @@ -161,6 +166,12 @@ public class CommandOptions implements ICommandOptions { "For remote sharded invocation, whether or not to attempt the setup in parallel.") private boolean mUseParallelRemoteSetup = false; + @Option(name = "parallel-setup", description = "Whether to attempt the setup in parallel.") + private boolean mUseParallelSetup = false; + + @Option(name = "parallel-setup-timeout", description = "Timeout to use during parallel setup.") + private Duration mParallelSetupTimeout = Duration.ofMinutes(30L); + @Option( name = "replicate-parent-setup", description = @@ -370,6 +381,14 @@ public class CommandOptions implements ICommandOptions { * {@inheritDoc} */ @Override + public boolean getOptimizeMainlineTest() { + return mOptimizeMainlineTest; + } + + /** + * {@inheritDoc} + */ + @Override public Integer getShardCount() { return mShardCount; } @@ -514,6 +533,18 @@ public class CommandOptions implements ICommandOptions { /** {@inheritDoc} */ @Override + public boolean shouldUseParallelSetup() { + return mUseParallelSetup; + } + + /** {@inheritDoc} */ + @Override + public Duration getParallelSetupTimeout() { + return mParallelSetupTimeout; + } + + /** {@inheritDoc} */ + @Override public boolean shouldUseReplicateSetup() { return mReplicateParentSetup; } diff --git a/src/com/android/tradefed/command/CommandRunner.java b/src/com/android/tradefed/command/CommandRunner.java index fe77903cf..0cb94feab 100644 --- a/src/com/android/tradefed/command/CommandRunner.java +++ b/src/com/android/tradefed/command/CommandRunner.java @@ -21,6 +21,7 @@ import com.android.tradefed.clearcut.TerminateClearcutClient; import com.android.tradefed.config.ConfigurationException; import com.android.tradefed.config.GlobalConfiguration; import com.android.tradefed.device.NoDeviceException; +import com.android.tradefed.result.error.InfraErrorIdentifier; import com.android.tradefed.util.FileUtil; import com.android.tradefed.util.SerializationUtil; @@ -119,7 +120,10 @@ public class CommandRunner { // After 1 min we check if the command was executed. if (mScheduler.getReadyCommandCount() > 0 && mScheduler.getExecutingCommandCount() == 0) { - printStackTrace(new NoDeviceException("No device was allocated for the command.")); + printStackTrace( + new NoDeviceException( + "No device was allocated for the command.", + InfraErrorIdentifier.RUNNER_ALLOCATION_ERROR)); mErrorCode = ExitCode.NO_DEVICE_ALLOCATED; mScheduler.removeAllCommands(); mScheduler.shutdown(); diff --git a/src/com/android/tradefed/command/CommandScheduler.java b/src/com/android/tradefed/command/CommandScheduler.java index 722884090..0ee2ca021 100644 --- a/src/com/android/tradefed/command/CommandScheduler.java +++ b/src/com/android/tradefed/command/CommandScheduler.java @@ -69,6 +69,7 @@ import com.android.tradefed.log.LogRegistry; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.ResultForwarder; +import com.android.tradefed.result.error.InfraErrorIdentifier; import com.android.tradefed.result.suite.SuiteResultReporter; import com.android.tradefed.sandbox.ISandbox; import com.android.tradefed.testtype.IRemoteTest; @@ -1075,8 +1076,9 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm IConfiguration config = cmd.getConfiguration(); IInvocationContext context = new InvocationContext(); context.setConfigurationDescriptor(config.getConfigurationDescription()); - Map<String, ITestDevice> devices = allocateDevices(config, manager); - if (!devices.isEmpty()) { + DeviceAllocationResult allocationResults = allocateDevices(config, manager); + if (allocationResults.wasAllocationSuccessful()) { + Map<String, ITestDevice> devices = allocationResults.getAllocatedDevices(); cmdIter.remove(); mExecutingCommands.add(cmd); context.addAllocatedDevice(devices); @@ -1534,8 +1536,9 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm ExecutableCommand execCmd = createExecutableCommand(cmdTracker, config, false); context.setConfigurationDescriptor(config.getConfigurationDescription()); - Map<String, ITestDevice> devices = allocateDevices(config, manager); - if (!devices.isEmpty()) { + DeviceAllocationResult allocationResults = allocateDevices(config, manager); + if (allocationResults.wasAllocationSuccessful()) { + Map<String, ITestDevice> devices = allocationResults.getAllocatedDevices(); context.addAllocatedDevice(devices); synchronized (this) { mExecutingCommands.add(execCmd); @@ -1543,8 +1546,16 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm CLog.d("Executing '%s' on '%s'", cmdTracker.getArgs()[0], devices); startInvocation(context, execCmd, listener, new FreeDeviceHandler(manager)); } else { + // Log adb output just to help debug + String adbOutput = + ((DeviceManager) GlobalConfiguration.getDeviceManagerInstance()) + .executeGlobalAdbCommand("devices"); + CLog.e("'adb devices' output:\n%s", adbOutput); throw new NoDeviceException( - "no devices is available for command: " + Arrays.asList(args)); + String.format( + "no devices is available for command: %s\n%s", + Arrays.asList(args), allocationResults.formattedReason()), + InfraErrorIdentifier.SCHEDULER_ALLOCATION_ERROR); } } @@ -1585,11 +1596,12 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm /** * Allocate devices for a config. + * * @param config a {@link IConfiguration} has device requirements. * @param manager a {@link IDeviceManager} * @return allocated devices */ - Map<String, ITestDevice> allocateDevices(IConfiguration config, IDeviceManager manager) { + DeviceAllocationResult allocateDevices(IConfiguration config, IDeviceManager manager) { Map<String, ITestDevice> devices = new LinkedHashMap<String, ITestDevice>(); ITestDevice device = null; if (config.getDeviceConfig().isEmpty()) { @@ -1598,6 +1610,7 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm // If we need to replicate the setup on all devices ParentShardReplicate.replicatedSetup(config, getKeyStoreClient()); synchronized(this) { + DeviceAllocationResult allocationResults = new DeviceAllocationResult(); for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) { device = manager.allocateDevice( @@ -1605,6 +1618,9 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm if (device != null) { devices.put(deviceConfig.getDeviceName(), device); } else { + allocationResults.addAllocationFailureReason( + deviceConfig.getDeviceName(), + deviceConfig.getDeviceRequirements().getNoMatchReason()); // If one of the several device cannot be allocated, we de-allocate // all the previous one. for (ITestDevice allocatedDevice : devices.values()) { @@ -1623,7 +1639,8 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm break; } } - return devices; + allocationResults.addAllocatedDevices(devices); + return allocationResults; } } diff --git a/src/com/android/tradefed/command/DeviceAllocationResult.java b/src/com/android/tradefed/command/DeviceAllocationResult.java new file mode 100644 index 000000000..849b04a5f --- /dev/null +++ b/src/com/android/tradefed/command/DeviceAllocationResult.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tradefed.command; + +import com.android.tradefed.device.ITestDevice; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** Represents the results of an allocation attempt for a command. */ +public class DeviceAllocationResult { + + private Map<String, String> mNotAllocatedReason = new LinkedHashMap<>(); + private Map<String, ITestDevice> mAllocatedDevices = new LinkedHashMap<>(); + + /** returns whether or not the allocation was successful. */ + public boolean wasAllocationSuccessful() { + return !mAllocatedDevices.isEmpty(); + } + + /** Add devices that have been allocated. */ + public void addAllocatedDevices(Map<String, ITestDevice> devices) { + mAllocatedDevices.putAll(devices); + } + + /** Add the reasons for not being allocated for each device config. */ + public void addAllocationFailureReason(String deviceConfigName, Map<String, String> reasons) { + mNotAllocatedReason.put(deviceConfigName, createReasonMessage(reasons)); + } + + /** Returns the map of allocated devices */ + public Map<String, ITestDevice> getAllocatedDevices() { + return mAllocatedDevices; + } + + public String formattedReason() { + if (mNotAllocatedReason.size() == 1) { + return mNotAllocatedReason.values().iterator().next().toString(); + } + return mNotAllocatedReason.toString(); + } + + private String createReasonMessage(Map<String, String> reasons) { + StringBuilder sb = new StringBuilder(); + for (String serial : reasons.keySet()) { + String reason = reasons.get(serial); + if (reason == null) { + reason = "No reason provided"; + } + sb.append(String.format("device '%s': %s", serial, reason)); + } + return sb.toString(); + } +} diff --git a/src/com/android/tradefed/command/ICommandOptions.java b/src/com/android/tradefed/command/ICommandOptions.java index 37dd02215..ea2ce99f4 100644 --- a/src/com/android/tradefed/command/ICommandOptions.java +++ b/src/com/android/tradefed/command/ICommandOptions.java @@ -19,6 +19,7 @@ package com.android.tradefed.command; import com.android.tradefed.device.metric.AutoLogCollector; import com.android.tradefed.util.UniqueMultiMap; +import java.time.Duration; import java.util.Map; import java.util.Set; @@ -120,6 +121,10 @@ public interface ICommandOptions { */ public void setInvocationTimeout(Long mInvocationTimeout); + + /** Returns true if we should optimize the list of test modules for mainline test. */ + public boolean getOptimizeMainlineTest(); + /** * Return the total shard count for the command. */ @@ -185,6 +190,12 @@ public interface ICommandOptions { /** Whether or not to attempt parallel setup of the remote devices. */ public boolean shouldUseParallelRemoteSetup(); + /** Whether or not to attempt parallel setup. */ + public boolean shouldUseParallelSetup(); + + /** Returns the timeout to use during parallel setups. */ + public Duration getParallelSetupTimeout(); + /** Whether or not to use replicated setup for all the remote devices. */ public boolean shouldUseReplicateSetup(); diff --git a/src/com/android/tradefed/config/proxy/TradefedDelegator.java b/src/com/android/tradefed/config/proxy/TradefedDelegator.java index 19c9ae972..17af0196d 100644 --- a/src/com/android/tradefed/config/proxy/TradefedDelegator.java +++ b/src/com/android/tradefed/config/proxy/TradefedDelegator.java @@ -18,15 +18,17 @@ package com.android.tradefed.config.proxy; import com.android.tradefed.command.CommandOptions; import com.android.tradefed.config.ConfigurationException; import com.android.tradefed.config.Option; +import com.android.tradefed.util.FileUtil; import com.android.tradefed.util.UniqueMultiMap; import com.google.common.base.Joiner; import java.io.File; -import java.io.FileFilter; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; /** Objects that helps delegating the invocation to another Tradefed binary. */ public class TradefedDelegator { @@ -60,16 +62,8 @@ public class TradefedDelegator { } /** Creates the classpath out of the jars in the directory. */ - public String createClasspath() { - List<File> jars = - Arrays.asList( - mDelegatedTfRootDir.listFiles( - new FileFilter() { - @Override - public boolean accept(File pathname) { - return pathname.getName().endsWith(".jar"); - } - })); + public String createClasspath() throws IOException { + Set<File> jars = FileUtil.findFilesObject(mDelegatedTfRootDir, ".*\\.jar"); return Joiner.on(":").join(jars); } @@ -96,10 +90,17 @@ public class TradefedDelegator { */ public static String[] clearCommandline(String[] originalCommand) throws ConfigurationException { + String[] commandLine = clearCommandlineFromOneArg(originalCommand, DELETEGATED_OPTION_NAME); + return commandLine; + } + + /** Remove a given option from the command line. */ + private static String[] clearCommandlineFromOneArg(String[] originalCommand, String optionName) + throws ConfigurationException { List<String> argsList = new ArrayList<>(Arrays.asList(originalCommand)); try { - while (argsList.contains("--" + DELETEGATED_OPTION_NAME)) { - int index = argsList.indexOf("--" + DELETEGATED_OPTION_NAME); + while (argsList.contains("--" + optionName)) { + int index = argsList.indexOf("--" + optionName); if (index != -1) { argsList.remove(index + 1); argsList.remove(index); diff --git a/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java b/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java index 1aef60cc0..7a847710c 100644 --- a/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java +++ b/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java @@ -21,6 +21,7 @@ import com.android.tradefed.config.ConfigurationDef; import com.android.tradefed.config.ConfigurationException; import com.android.tradefed.config.OptionSetter; import com.android.tradefed.config.yaml.IDefaultObjectLoader.LoaderConfiguration; +import com.android.tradefed.config.yaml.YamlClassOptionsParser.ClassAndOptions; import com.google.common.collect.ImmutableList; @@ -40,6 +41,8 @@ import java.util.Set; public final class ConfigurationYamlParser { private static final String DESCRIPTION_KEY = "description"; + public static final String PRE_SETUP_ACTION_KEY = "pre_setup_action"; + public static final String POST_SETUP_ACTION_KEY = "post_setup_action"; public static final String DEPENDENCIES_KEY = "dependencies"; public static final String TESTS_KEY = "tests"; @@ -89,6 +92,16 @@ public final class ConfigurationYamlParser { mSeenKeys.add(DESCRIPTION_KEY); } Set<String> dependencyFiles = new LinkedHashSet<>(); + if (yamlObjects.containsKey(PRE_SETUP_ACTION_KEY)) { + YamlClassOptionsParser classAndOptions = + new YamlClassOptionsParser( + "action", + PRE_SETUP_ACTION_KEY, + (List<Map<String, Object>>) yamlObjects.get(PRE_SETUP_ACTION_KEY)); + mSeenKeys.add(PRE_SETUP_ACTION_KEY); + convertClassAndOptionsToObjects( + configDef, classAndOptions, Configuration.TARGET_PREPARER_TYPE_NAME); + } if (yamlObjects.containsKey(DEPENDENCIES_KEY)) { YamlTestDependencies testDeps = new YamlTestDependencies( @@ -97,12 +110,24 @@ public final class ConfigurationYamlParser { mSeenKeys.add(DEPENDENCIES_KEY); } if (yamlObjects.containsKey(TESTS_KEY)) { - YamlTestRunners runnerInfo = - new YamlTestRunners((List<Map<String, Object>>) yamlObjects.get(TESTS_KEY)); + YamlClassOptionsParser runnerInfo = + new YamlClassOptionsParser( + "test", + TESTS_KEY, + (List<Map<String, Object>>) yamlObjects.get(TESTS_KEY)); mSeenKeys.add(TESTS_KEY); - convertTestsToObjects(configDef, runnerInfo); + convertClassAndOptionsToObjects(configDef, runnerInfo, Configuration.TEST_TYPE_NAME); + } + if (yamlObjects.containsKey(POST_SETUP_ACTION_KEY)) { + YamlClassOptionsParser runnerInfo = + new YamlClassOptionsParser( + "action", + POST_SETUP_ACTION_KEY, + (List<Map<String, Object>>) yamlObjects.get(POST_SETUP_ACTION_KEY)); + mSeenKeys.add(POST_SETUP_ACTION_KEY); + convertClassAndOptionsToObjects( + configDef, runnerInfo, Configuration.TARGET_PREPARER_TYPE_NAME); } - if (!mSeenKeys.containsAll(REQUIRED_KEYS)) { Set<String> missingKeys = new HashSet<>(REQUIRED_KEYS); missingKeys.removeAll(mSeenKeys); @@ -185,27 +210,26 @@ public final class ConfigurationYamlParser { return dependencies; } - private void convertTestsToObjects(ConfigurationDef def, YamlTestRunners tests) { - if (tests.getRunner() == null) { + private void convertClassAndOptionsToObjects( + ConfigurationDef def, YamlClassOptionsParser tests, String configObjType) { + if (tests.getClassesAndOptions().isEmpty()) { return; } - String className = tests.getRunner(); - int classCount = def.addConfigObjectDef(Configuration.TEST_TYPE_NAME, className); - for (Entry<String, String> options : tests.getOptions().entries()) { - String optionName = - String.format( - "%s%c%d%c%s", - className, - OptionSetter.NAMESPACE_SEPARATOR, - classCount, - OptionSetter.NAMESPACE_SEPARATOR, - options.getKey()); - def.addOptionDef( - optionName, - null, - options.getValue(), - def.getName(), - Configuration.TEST_TYPE_NAME); + for (ClassAndOptions classOptions : tests.getClassesAndOptions()) { + String className = classOptions.mClass; + int classCount = def.addConfigObjectDef(configObjType, className); + for (Entry<String, String> options : classOptions.mOptions.entries()) { + String optionName = + String.format( + "%s%c%d%c%s", + className, + OptionSetter.NAMESPACE_SEPARATOR, + classCount, + OptionSetter.NAMESPACE_SEPARATOR, + options.getKey()); + def.addOptionDef( + optionName, null, options.getValue(), def.getName(), configObjType); + } } } } diff --git a/src/com/android/tradefed/config/yaml/YamlTestRunners.java b/src/com/android/tradefed/config/yaml/YamlClassOptionsParser.java index 1c8952e6b..3dcdae1fc 100644 --- a/src/com/android/tradefed/config/yaml/YamlTestRunners.java +++ b/src/com/android/tradefed/config/yaml/YamlClassOptionsParser.java @@ -20,57 +20,54 @@ import com.android.tradefed.config.ConfigurationException; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Multimap; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** Helper to parse test runner information from the YAML Tradefed Configuration. */ -public class YamlTestRunners { +public class YamlClassOptionsParser { - private static final String TEST_KEY = "test"; - private static final String TEST_NAME_KEY = "name"; + private static final String CLASS_NAME_KEY = "name"; private static final String OPTIONS_KEY = "options"; - private String mRunner; - private Multimap<String, String> mOptions = LinkedListMultimap.create(); + class ClassAndOptions { + public String mClass; + public Multimap<String, String> mOptions = LinkedListMultimap.create(); + } - public YamlTestRunners(List<Map<String, Object>> tests) throws ConfigurationException { - if (tests.size() > 1) { - throw new ConfigurationException("Currently only support one runner at a time."); - } + private List<ClassAndOptions> mListClassAndOptions = new ArrayList<>(); + + public YamlClassOptionsParser(String mainkey, String category, List<Map<String, Object>> tests) + throws ConfigurationException { for (Map<String, Object> runnerEntry : tests) { - if (runnerEntry.containsKey(TEST_KEY)) { + if (runnerEntry.containsKey(mainkey)) { + ClassAndOptions classOptions = new ClassAndOptions(); + mListClassAndOptions.add(classOptions); for (Entry<String, Object> entry : - ((Map<String, Object>) runnerEntry.get(TEST_KEY)).entrySet()) { - if (TEST_NAME_KEY.equals(entry.getKey())) { - mRunner = (String) entry.getValue(); + ((Map<String, Object>) runnerEntry.get(mainkey)).entrySet()) { + if (CLASS_NAME_KEY.equals(entry.getKey())) { + classOptions.mClass = (String) entry.getValue(); } if (OPTIONS_KEY.equals(entry.getKey())) { for (Map<String, Object> optionMap : (List<Map<String, Object>>) entry.getValue()) { for (Entry<String, Object> optionVal : optionMap.entrySet()) { // TODO: Support map option - mOptions.put(optionVal.getKey(), optionVal.getValue().toString()); + classOptions.mOptions.put( + optionVal.getKey(), optionVal.getValue().toString()); } } } } } else { throw new ConfigurationException( - String.format( - "'%s' key is mandatory in '%s'", - TEST_KEY, ConfigurationYamlParser.TESTS_KEY)); + String.format("'%s' key is mandatory in '%s'", mainkey, category)); } } } - /** Returns the test runner to be used. */ - public String getRunner() { - return mRunner; - } - - /** Returns the options for the test runner */ - public Multimap<String, String> getOptions() { - return mOptions; + public List<ClassAndOptions> getClassesAndOptions() { + return mListClassAndOptions; } } diff --git a/src/com/android/tradefed/device/DeviceSelectionOptions.java b/src/com/android/tradefed/device/DeviceSelectionOptions.java index 6dbae6d5a..fac18c72e 100644 --- a/src/com/android/tradefed/device/DeviceSelectionOptions.java +++ b/src/com/android/tradefed/device/DeviceSelectionOptions.java @@ -29,6 +29,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -161,6 +162,8 @@ public class DeviceSelectionOptions implements IDeviceSelection { // If we have tried to fetch the environment variable ANDROID_SERIAL before. private boolean mFetchedEnvVariable = false; + // Store the reason for which the device was not matched. + private Map<String, String> mNoMatchReason = new LinkedHashMap<>(); private static final String VARIANT_SEPARATOR = ":"; @@ -448,6 +451,7 @@ public class DeviceSelectionOptions implements IDeviceSelection { */ @Override public boolean matches(IDevice device) { + String deviceSerial = device.getSerialNumber(); Collection<String> serials = getSerials(device); Collection<String> excludeSerials = getExcludeSerials(); Map<String, Collection<String>> productVariants = splitOnVariant(getProductTypes()); @@ -456,9 +460,17 @@ public class DeviceSelectionOptions implements IDeviceSelection { if (!serials.isEmpty() && !serials.contains(device.getSerialNumber())) { + addNoMatchReason( + deviceSerial, + String.format( + "device serial does not match any requested serial(%s)", serials)); return false; } if (excludeSerials.contains(device.getSerialNumber())) { + addNoMatchReason( + deviceSerial, + String.format( + "device serial was part of excluded serials(%s)", excludeSerials)); return false; } if (!productTypes.isEmpty()) { @@ -468,15 +480,31 @@ public class DeviceSelectionOptions implements IDeviceSelection { String productVariant = getDeviceProductVariant(device); Collection<String> variants = productVariants.get(productType); if (variants != null && !variants.contains(productVariant)) { + addNoMatchReason( + deviceSerial, + String.format( + "device variant (%s) does not match requested variants(%s)", + productVariant, variants)); return false; } } else { // no product type matches; bye-bye + addNoMatchReason( + deviceSerial, + String.format( + "device product type (%s) does not match requested product types(%s)", + productType, productTypes)); return false; } } for (Map.Entry<String, String> propEntry : properties.entrySet()) { - if (!propEntry.getValue().equals(device.getProperty(propEntry.getKey()))) { + String deviceProperty = device.getProperty(propEntry.getKey()); + if (!propEntry.getValue().equals(deviceProperty)) { + addNoMatchReason( + deviceSerial, + String.format( + "device property (%s) value(%s) does not match requested value(%s)", + propEntry.getKey(), deviceProperty, propEntry.getValue())); return false; } } @@ -488,12 +516,25 @@ public class DeviceSelectionOptions implements IDeviceSelection { if ((mMinSdk != null) || (mMaxSdk != null)) { int deviceSdkLevel = getDeviceSdkLevel(device); if (deviceSdkLevel < 0) { + addNoMatchReason( + deviceSerial, + String.format("device returned unexpected sdk level (%s)", deviceSdkLevel)); return false; } if (mMinSdk != null && deviceSdkLevel < mMinSdk) { + addNoMatchReason( + deviceSerial, + String.format( + "device sdk (%s) is below the requested min sdk (%s)", + deviceSdkLevel, mMinSdk)); return false; } if (mMaxSdk != null && mMaxSdk < deviceSdkLevel) { + addNoMatchReason( + deviceSerial, + String.format( + "device sdk (%s) is above the requested max sdk (%s)", + deviceSdkLevel, mMaxSdk)); return false; } } @@ -505,19 +546,35 @@ public class DeviceSelectionOptions implements IDeviceSelection { if (device instanceof StubDevice || device instanceof FastbootDevice) { // Reading battery of fastboot and StubDevice device does not work and could // lead to weird log. + addNoMatchReason( + deviceSerial, + String.format( + "device type is (%s) which cannot have a battery required.", + device.getClass())); return false; } Integer deviceBattery = getBatteryLevel(device); if (deviceBattery == null) { // Couldn't determine battery level when that check is required; reject device + addNoMatchReason(deviceSerial, "device failed to return a battery reading."); return false; } if (isLessAndNotNull(deviceBattery, mMinBattery)) { // deviceBattery < mMinBattery + addNoMatchReason( + deviceSerial, + String.format( + "device battery (%s) is below the requested min battery (%s)", + deviceBattery, mMinBattery)); return false; } if (isLessEqAndNotNull(mMaxBattery, deviceBattery)) { // mMaxBattery <= deviceBattery + addNoMatchReason( + deviceSerial, + String.format( + "device battery (%s) is above the requested max battery (%s)", + deviceBattery, mMaxBattery)); return false; } } @@ -558,39 +615,55 @@ public class DeviceSelectionOptions implements IDeviceSelection { if ((emulatorRequested() || stubEmulatorRequested()) && !device.isEmulator()) { return false; } + String deviceSerial = device.getSerialNumber(); // If physical device is requested but device is emulator or remote ip device, skip if (deviceRequested() && (device.isEmulator() || RemoteAndroidDevice.checkSerialFormatValid(device.getSerialNumber()))) { + addNoMatchReason(deviceSerial, "device is not a physical device"); return false; } if (mRequestedType != null) { Class<?> classNeeded = mRequestedType.getRequiredClass(); if (!device.getClass().equals(classNeeded)) { + addNoMatchReason( + deviceSerial, + String.format( + "device is type (%s) while requested type was (%s)", + device.getClass(), classNeeded)); return false; } } else { if (device.isEmulator() && (device instanceof StubDevice) && !stubEmulatorRequested()) { // only allocate the stub emulator if requested + addNoMatchReason(deviceSerial, "device is emulator while requested type was not"); return false; } if (nullDeviceRequested() != (device instanceof NullDevice)) { + addNoMatchReason( + deviceSerial, "device is null-device while requested type was not"); return false; } if (tcpDeviceRequested() != TcpDevice.class.equals(device.getClass())) { // We only match an exact TcpDevice here, no child class. + addNoMatchReason(deviceSerial, "device is tcp-device while requested type was not"); return false; } if (gceDeviceRequested() != RemoteAvdIDevice.class.equals(device.getClass())) { // We only match an exact RemoteAvdIDevice here, no child class. + addNoMatchReason(deviceSerial, "device is gce-device while requested type was not"); return false; } if (remoteDeviceRequested() != VmRemoteDevice.class.equals(device.getClass())) { + addNoMatchReason( + deviceSerial, "device is remote-device while requested type was not"); return false; } if (localVirtualDeviceRequested() != StubLocalAndroidVirtualDevice.class.equals(device.getClass())) { + addNoMatchReason( + deviceSerial, "device is local-virtual while requested type was not"); return false; } } @@ -704,6 +777,15 @@ public class DeviceSelectionOptions implements IDeviceSelection { return apiLevel; } + private void addNoMatchReason(String device, String reason) { + mNoMatchReason.put(device, reason); + } + + @Override + public Map<String, String> getNoMatchReason() { + return mNoMatchReason; + } + /** * Helper factory method to create a {@link IDeviceSelection} that will only match device * with given serial diff --git a/src/com/android/tradefed/device/IDeviceSelection.java b/src/com/android/tradefed/device/IDeviceSelection.java index c61179ed8..1302532a6 100644 --- a/src/com/android/tradefed/device/IDeviceSelection.java +++ b/src/com/android/tradefed/device/IDeviceSelection.java @@ -116,4 +116,10 @@ public interface IDeviceSelection extends IMatcher<IDevice> { */ public void setSerial(String... serialNumber); + /** + * Returns the reason for which the device was not matched. + * + * @return a Map of serial number to reason for which it wasn't allocated + */ + public Map<String, String> getNoMatchReason(); } diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java index 8c9616264..3bd9abe61 100644 --- a/src/com/android/tradefed/device/NativeDevice.java +++ b/src/com/android/tradefed/device/NativeDevice.java @@ -109,6 +109,7 @@ import javax.annotation.concurrent.GuardedBy; public class NativeDevice implements IManagedTestDevice { protected static final String SD_CARD = "/sdcard/"; + protected static final String STORAGE_EMULATED = "/storage/emulated/"; /** * Allow pauses of up to 2 minutes while receiving bugreport. * <p/> @@ -1101,7 +1102,7 @@ public class NativeDevice implements IManagedTestDevice { public boolean pullFile(final String remoteFilePath, final File localFile) throws DeviceNotAvailableException { - if (remoteFilePath.startsWith(SD_CARD)) { + if (isSdcardOrEmulated(remoteFilePath)) { ContentProviderHandler handler = getContentProvider(); if (handler != null) { return handler.pullFile(remoteFilePath, localFile); @@ -1209,7 +1210,7 @@ public class NativeDevice implements IManagedTestDevice { @Override public boolean pushFile(final File localFile, final String remoteFilePath) throws DeviceNotAvailableException { - if (remoteFilePath.startsWith(SD_CARD)) { + if (isSdcardOrEmulated(remoteFilePath)) { ContentProviderHandler handler = getContentProvider(); if (handler != null) { return handler.pushFile(localFile, remoteFilePath); @@ -1284,6 +1285,15 @@ public class NativeDevice implements IManagedTestDevice { /** {@inheritDoc} */ @Override public boolean doesFileExist(String deviceFilePath) throws DeviceNotAvailableException { + if (isSdcardOrEmulated(deviceFilePath)) { + ContentProviderHandler handler = getContentProvider(); + if (handler != null) { + CLog.d("Delegating check to ContentProvider doesFileExist(%s)", deviceFilePath); + + return handler.doesFileExist(deviceFilePath); + } + } + CLog.d("Using 'ls' to check doesFileExist(%s)", deviceFilePath); String lsGrep = executeShellCommand(String.format("ls \"%s\"", deviceFilePath)); return !lsGrep.contains("No such file or directory"); } @@ -1291,7 +1301,7 @@ public class NativeDevice implements IManagedTestDevice { /** {@inheritDoc} */ @Override public void deleteFile(String deviceFilePath) throws DeviceNotAvailableException { - if (deviceFilePath.startsWith(SD_CARD)) { + if (isSdcardOrEmulated(deviceFilePath)) { ContentProviderHandler handler = getContentProvider(); if (handler != null) { if (handler.deleteFile(deviceFilePath)) { @@ -1624,7 +1634,7 @@ public class NativeDevice implements IManagedTestDevice { @Override public boolean pullDir(String deviceFilePath, File localDir) throws DeviceNotAvailableException { - if (deviceFilePath.startsWith(SD_CARD)) { + if (isSdcardOrEmulated(deviceFilePath)) { ContentProviderHandler handler = getContentProvider(); if (handler != null) { return handler.pullDir(deviceFilePath, localDir); @@ -1677,6 +1687,11 @@ public class NativeDevice implements IManagedTestDevice { return true; } + /** Checks whether path is external storage path. */ + private boolean isSdcardOrEmulated(String path) { + return path.startsWith(SD_CARD) || path.startsWith(STORAGE_EMULATED); + } + /** * {@inheritDoc} */ @@ -4051,7 +4066,8 @@ public class NativeDevice implements IManagedTestDevice { throw new DeviceRuntimeException( String.format( "Failed to query property '%s'. device returned null.", - DeviceProperties.BUILD_CODENAME)); + DeviceProperties.BUILD_CODENAME), + DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE); } codeName = codeName.trim(); int apiLevel = getApiLevel() + ("REL".equals(codeName) ? 0 : 1); diff --git a/src/com/android/tradefed/device/NoDeviceException.java b/src/com/android/tradefed/device/NoDeviceException.java index 45f433912..a2fae9330 100644 --- a/src/com/android/tradefed/device/NoDeviceException.java +++ b/src/com/android/tradefed/device/NoDeviceException.java @@ -16,36 +16,23 @@ package com.android.tradefed.device; import com.android.tradefed.build.BuildSerializedVersion; +import com.android.tradefed.error.HarnessRuntimeException; +import com.android.tradefed.result.error.ErrorIdentifier; -/** - * Thrown when there's no device to execute a given command. - */ -public class NoDeviceException extends Exception { - private static final long serialVersionUID = BuildSerializedVersion.VERSION; - - /** - * Creates a {@link NoDeviceException}. - */ - public NoDeviceException() { - super(); - } +import java.lang.StackWalker.Option; - /** - * Creates a {@link NoDeviceException}. - * - * @param msg a descriptive message. - */ - public NoDeviceException(String msg) { - super(msg); - } +/** Thrown when there's no device to execute a given command. */ +public class NoDeviceException extends HarnessRuntimeException { + private static final long serialVersionUID = BuildSerializedVersion.VERSION; /** * Creates a {@link NoDeviceException}. * * @param msg a descriptive message. - * @param cause the root {@link Throwable} that caused the device to become unavailable. + * @param errorId The {@link ErrorIdentifier} categorizing the exception. */ - public NoDeviceException(String msg, Throwable cause) { - super(msg, cause); + public NoDeviceException(String msg, ErrorIdentifier errorId) { + super(msg, errorId); + setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass()); } } diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java index 80fb98d1c..958f21df6 100644 --- a/src/com/android/tradefed/device/TestDevice.java +++ b/src/com/android/tradefed/device/TestDevice.java @@ -27,6 +27,7 @@ import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.result.ByteArrayInputStreamSource; import com.android.tradefed.result.FileInputStreamSource; import com.android.tradefed.result.InputStreamSource; +import com.android.tradefed.result.error.DeviceErrorIdentifier; import com.android.tradefed.util.AaptParser; import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; @@ -1187,7 +1188,8 @@ public class TestDevice extends NativeDevice { String[] lines = commandOutput.split("\\r?\\n"); if (!lines[0].equals("Users:")) { throw new DeviceRuntimeException( - String.format("'%s' in not a valid output for 'pm list users'", commandOutput)); + String.format("'%s' in not a valid output for 'pm list users'", commandOutput), + DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE); } ArrayList<String[]> users = new ArrayList<String[]>(lines.length - 1); for (int i = 1; i < lines.length; i++) { @@ -1199,7 +1201,8 @@ public class TestDevice extends NativeDevice { String.format( "device output: '%s' \nline: '%s' was not in the expected " + "format for user info.", - commandOutput, lines[i])); + commandOutput, lines[i]), + DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE); } users.add(tokens); } diff --git a/src/com/android/tradefed/device/cloud/GceManager.java b/src/com/android/tradefed/device/cloud/GceManager.java index 4a323c1d9..a20688423 100644 --- a/src/com/android/tradefed/device/cloud/GceManager.java +++ b/src/com/android/tradefed/device/cloud/GceManager.java @@ -26,7 +26,7 @@ import com.android.tradefed.result.ByteArrayInputStreamSource; import com.android.tradefed.result.FileInputStreamSource; import com.android.tradefed.result.InputStreamSource; import com.android.tradefed.result.LogDataType; -import com.android.tradefed.result.error.DeviceErrorIdentifier; +import com.android.tradefed.result.error.InfraErrorIdentifier; import com.android.tradefed.targetprep.TargetSetupError; import com.android.tradefed.util.ArrayUtil; import com.android.tradefed.util.CommandResult; @@ -221,7 +221,7 @@ public class GceManager { throw new TargetSetupError( String.format("acloud errors: %s", errors), mDeviceDescriptor, - DeviceErrorIdentifier.FAILED_TO_LAUNCH_GCE); + InfraErrorIdentifier.NO_ACLOUD_REPORT); } } mGceAvdInfo = @@ -229,7 +229,11 @@ public class GceManager { reportFile, mDeviceDescriptor, mDeviceOptions.getRemoteAdbPort()); return mGceAvdInfo; } catch (IOException e) { - throw new TargetSetupError("failed to create log file", e, mDeviceDescriptor); + throw new TargetSetupError( + "failed to create log file", + e, + mDeviceDescriptor, + InfraErrorIdentifier.FAIL_TO_CREATE_FILE); } finally { FileUtil.deleteFile(reportFile); } diff --git a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java index 4925bb624..878e11430 100644 --- a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java +++ b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java @@ -37,6 +37,8 @@ import com.android.tradefed.result.InputStreamSource; import com.android.tradefed.result.LogDataType; import com.android.tradefed.result.error.DeviceErrorIdentifier; import com.android.tradefed.targetprep.TargetSetupError; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; import com.android.tradefed.util.FileUtil; import com.android.tradefed.util.StreamUtil; @@ -63,6 +65,7 @@ public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements I private GceManager mGceHandler = null; private GceSshTunnelMonitor mGceSshMonitor; + private DeviceNotAvailableException mTunnelInitFailed = null; private static final long WAIT_FOR_TUNNEL_ONLINE = 2 * 60 * 1000; private static final long WAIT_AFTER_REBOOT = 60 * 1000; @@ -91,6 +94,7 @@ public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements I try { mGceAvd = null; mGceSshMonitor = null; + mTunnelInitFailed = null; // We create a brand new GceManager each time to ensure clean state. mGceHandler = new GceManager(getDeviceDescriptor(), getOptions(), info); getGceHandler().logStableHostImageInfos(info); @@ -269,7 +273,10 @@ public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements I String.format( "Device failed to boot. Error from Acloud: %s", mGceAvd.getErrors()); - throw new TargetSetupError(errorMsg, getDeviceDescriptor()); + throw new TargetSetupError( + errorMsg, + getDeviceDescriptor(), + DeviceErrorIdentifier.FAILED_TO_LAUNCH_GCE); } } createGceSshMonitor(this, buildInfo, mGceAvd.hostAndPort(), this.getOptions()); @@ -322,14 +329,20 @@ public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements I } getRunUtil().sleep(RETRY_INTERVAL_MS); } - throw new DeviceNotAvailableException( - String.format("Tunnel did not come back online after %sms", waitTime), - getSerialNumber(), - DeviceErrorIdentifier.FAILED_TO_CONNECT_TO_GCE); + mTunnelInitFailed = + new DeviceNotAvailableException( + String.format("Tunnel did not come back online after %sms", waitTime), + getSerialNumber(), + DeviceErrorIdentifier.FAILED_TO_CONNECT_TO_GCE); + throw mTunnelInitFailed; } @Override public void recoverDevice() throws DeviceNotAvailableException { + if (getGceSshMonitor() == null && mTunnelInitFailed != null) { + // We threw before but was not reported, so throw the root cause here. + throw mTunnelInitFailed; + } // Re-init tunnel when attempting recovery CLog.i("Attempting recovery on GCE AVD %s", getSerialNumber()); getGceSshMonitor().closeConnection(); @@ -447,4 +460,38 @@ public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements I } return descriptor; } + + /** + * Attempt to powerwash a GCE instance + * + * @return returns true if powerwash Gce success. + * @throws TargetSetupError + * @throws DeviceNotAvailableException + */ + public boolean powerwashGce() throws TargetSetupError, DeviceNotAvailableException { + if (mGceAvd == null) { + String errorMsg = String.format("Can not get GCE AVD Info. launch GCE first?"); + throw new TargetSetupError( + errorMsg, getDeviceDescriptor(), DeviceErrorIdentifier.DEVICE_UNAVAILABLE); + } + String username = this.getOptions().getInstanceUser(); + String powerwashCommand = String.format("/home/%s/bin/powerwash_cvd", username); + CommandResult powerwashRes = + GceManager.remoteSshCommandExecution( + mGceAvd, + this.getOptions(), + getRunUtil(), + 60000L, + powerwashCommand.split(" ")); + if (!CommandStatus.SUCCESS.equals(powerwashRes.getStatus())) { + CLog.e("%s", powerwashRes.getStderr()); + // Log 'adb devices' to confirm device is gone + CommandResult printAdbDevices = getRunUtil().runTimedCmd(60000L, "adb", "devices"); + CLog.e("%s\n%s", printAdbDevices.getStdout(), printAdbDevices.getStderr()); + // Proceed here, device could have been already gone. + return false; + } + getMonitor().waitForDeviceAvailable(); + return true; + } } diff --git a/src/com/android/tradefed/device/metric/FilePullerLogCollector.java b/src/com/android/tradefed/device/metric/FilePullerLogCollector.java index 547443387..da8c2408a 100644 --- a/src/com/android/tradefed/device/metric/FilePullerLogCollector.java +++ b/src/com/android/tradefed/device/metric/FilePullerLogCollector.java @@ -46,6 +46,8 @@ public class FilePullerLogCollector extends FilePullerDeviceMetricCollector { type = LogDataType.PB; } else if (".mp4".equals(ext)) { type = LogDataType.MP4; + } else if (".hprof".equals(ext)) { + type = LogDataType.HPROF; } testLog(metricFile.getName(), type, source); } diff --git a/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java b/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java index 1b071ef56..2255ee7fe 100644 --- a/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java +++ b/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java @@ -33,6 +33,8 @@ import com.android.tradefed.result.error.InfraErrorIdentifier; import com.android.tradefed.testtype.coverage.CoverageOptions; import com.android.tradefed.util.FileUtil; import com.android.tradefed.util.JavaCodeCoverageFlusher; +import com.android.tradefed.util.ProcessInfo; +import com.android.tradefed.util.PsParser; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; @@ -42,6 +44,7 @@ import org.jacoco.core.tools.ExecFileLoader; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Map; import java.util.List; @@ -148,7 +151,6 @@ public final class JavaCodeCoverageCollector extends BaseDeviceMetricCollector devicePaths.addAll(Splitter.on('\n').omitEmptyStrings().split(fileList)); collectAndLogCoverageMeasurementsAsRoot(device, devicePaths.build()); - } catch (DeviceNotAvailableException | IOException e) { throw new RuntimeException(e); } @@ -191,9 +193,24 @@ public final class JavaCodeCoverageCollector extends BaseDeviceMetricCollector private void collectAndLogCoverageMeasurements(ITestDevice device, List<String> devicePaths) throws IOException, DeviceNotAvailableException { + List<Integer> activePids = getRunningProcessIds(device); for (String devicePath : devicePaths) { File coverageFile = device.pullFile(devicePath); + + if (devicePath.endsWith(".mm.ec")) { + // Check if the process was still running. The file will have the format + // /data/misc/trace/jacoco-XXXXX.mm.ec where XXXXX is the process id. + int start = devicePath.indexOf('-') + 1; + int end = devicePath.indexOf('.'); + int pid = Integer.parseInt(devicePath.substring(start, end)); + if (!activePids.contains(pid)) { + device.deleteFile(devicePath); + } + } else { + device.deleteFile(devicePath); + } + verifyNotNull( coverageFile, "Failed to pull the Java code coverage file from %s", devicePath); @@ -216,6 +233,17 @@ public final class JavaCodeCoverageCollector extends BaseDeviceMetricCollector } } + private List<Integer> getRunningProcessIds(ITestDevice device) + throws DeviceNotAvailableException { + List<ProcessInfo> processes = PsParser.getProcesses(device.executeShellCommand("ps -e")); + List<Integer> pids = new ArrayList<>(); + + for (ProcessInfo process : processes) { + pids.add(process.getPid()); + } + return pids; + } + private FailureDescription createCodeCoverageFailure(String message) { return CurrentInvocation.createFailure(message, InfraErrorIdentifier.CODE_COVERAGE_ERROR); } diff --git a/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java b/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java index c4c969771..56292d2ec 100644 --- a/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java +++ b/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java @@ -24,6 +24,7 @@ import com.android.tradefed.device.ITestDevice; import com.android.tradefed.error.HarnessRuntimeException; import com.android.tradefed.invoker.TestInvocation.Stage; import com.android.tradefed.log.ITestLogger; +import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.result.FileInputStreamSource; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.LogDataType; @@ -131,6 +132,7 @@ public class DelegatedInvocationExecution extends InvocationExecution { IRunUtil runUtil = createRunUtil(receiver.getSocketServerPort()); CommandResult result = null; RuntimeException runtimeException = null; + CLog.d("Command line: %s", commandLine); try { result = runUtil.runTimedCmd( @@ -159,7 +161,7 @@ public class DelegatedInvocationExecution extends InvocationExecution { } if (result.getStatus().equals(CommandStatus.TIMED_OUT)) { throw new HarnessRuntimeException( - "Delegated invocation timed out.", InfraErrorIdentifier.UNDETERMINED); + "Delegated invocation timed out.", InfraErrorIdentifier.INVOCATION_TIMEOUT); } } finally { StreamUtil.close(mStderr); diff --git a/src/com/android/tradefed/invoker/InvocationExecution.java b/src/com/android/tradefed/invoker/InvocationExecution.java index 24d809e20..7a4ae8a00 100644 --- a/src/com/android/tradefed/invoker/InvocationExecution.java +++ b/src/com/android/tradefed/invoker/InvocationExecution.java @@ -83,6 +83,7 @@ import com.google.common.base.Strings; import java.io.File; import java.io.IOException; +import java.time.Duration; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -231,9 +232,10 @@ public class InvocationExecution implements IInvocationExecution { mTrackTargetPreparers = new ConcurrentHashMap<>(); int index = 0; - if (config.getCommandOptions().shouldUseReplicateSetup() + if ((config.getCommandOptions().shouldUseParallelSetup() + || config.getCommandOptions().shouldUseReplicateSetup()) && config.getDeviceConfig().size() > 1) { - CLog.d("Using parallel setup due to replicated setup enabled."); + CLog.d("Using parallel setup."); ParallelDeviceExecutor<Boolean> executor = new ParallelDeviceExecutor<>(testInfo.getContext().getDevices().size()); List<Callable<Boolean>> callableTasks = new ArrayList<>(); @@ -252,8 +254,8 @@ public class InvocationExecution implements IInvocationExecution { callableTasks.add(callableTask); index++; } - // Run setup with 30 minutes right now. - executor.invokeAll(callableTasks, 30, TimeUnit.MINUTES); + Duration timeout = config.getCommandOptions().getParallelSetupTimeout(); + executor.invokeAll(callableTasks, timeout.toMillis(), TimeUnit.MILLISECONDS); if (executor.hasErrors()) { List<Throwable> errors = executor.getErrors(); // TODO: Handle throwing multi-exceptions, right now throw the first one. diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java index 9da66eb1b..c4ffc43e0 100644 --- a/src/com/android/tradefed/invoker/TestInvocation.java +++ b/src/com/android/tradefed/invoker/TestInvocation.java @@ -43,6 +43,7 @@ import com.android.tradefed.device.cloud.ManagedRemoteDevice; import com.android.tradefed.device.cloud.NestedRemoteDevice; import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice; import com.android.tradefed.error.HarnessException; +import com.android.tradefed.error.HarnessRuntimeException; import com.android.tradefed.error.IHarnessException; import com.android.tradefed.guice.InvocationScope; import com.android.tradefed.invoker.logger.CurrentInvocation; @@ -164,6 +165,7 @@ public class TestInvocation implements ITestInvocation { private Long mStopRequestTime = null; private boolean mTestStarted = false; private boolean mInvocationFailed = false; + private boolean mDelegatedInvocation = false; private List<IScheduledInvocationListener> mSchedulerListeners = new ArrayList<>(); /** @@ -214,7 +216,6 @@ public class TestInvocation implements ITestInvocation { throws Throwable { ReportHostLog reportThread = new ReportHostLog(listener, config); Runtime.getRuntime().addShutdownHook(reportThread); - boolean resumed = false; String bugreportName = null; long startTime = System.currentTimeMillis(); long elapsedTime = -1; @@ -306,37 +307,39 @@ public class TestInvocation implements ITestInvocation { } CurrentInvocation.setActionInProgress(ActionInProgress.TEAR_DOWN); getRunUtil().allowInterrupt(false); - if (config.getCommandOptions().takeBugreportOnInvocationEnded() || - config.getCommandOptions().takeBugreportzOnInvocationEnded()) { - if (bugreportName != null) { - CLog.i("Bugreport to be taken for failure instead of invocation ended."); - } else { - bugreportName = INVOCATION_ENDED_BUGREPORT_NAME; - } - } - if (bugreportName != null) { - if (context.getDevices().size() == 1 || badDevice != null) { - ITestDevice collectBugreport = badDevice; - if (collectBugreport == null) { - collectBugreport = context.getDevices().get(0); + if (!mDelegatedInvocation) { + if (config.getCommandOptions().takeBugreportOnInvocationEnded() + || config.getCommandOptions().takeBugreportzOnInvocationEnded()) { + if (bugreportName != null) { + CLog.i("Bugreport to be taken for failure instead of invocation ended."); + } else { + bugreportName = INVOCATION_ENDED_BUGREPORT_NAME; } - // If we have identified a faulty device only take the bugreport on it. - takeBugreport(collectBugreport, listener, bugreportName); - } else if (context.getDevices().size() > 1) { - ParallelDeviceExecutor<Boolean> executor = - new ParallelDeviceExecutor<>(context.getDevices().size()); - List<Callable<Boolean>> callableTasks = new ArrayList<>(); - final String reportName = bugreportName; - for (ITestDevice device : context.getDevices()) { - Callable<Boolean> callableTask = - () -> { - takeBugreport(device, listener, reportName); - return true; - }; - callableTasks.add(callableTask); + } + if (bugreportName != null) { + if (context.getDevices().size() == 1 || badDevice != null) { + ITestDevice collectBugreport = badDevice; + if (collectBugreport == null) { + collectBugreport = context.getDevices().get(0); + } + // If we have identified a faulty device only take the bugreport on it. + takeBugreport(collectBugreport, listener, bugreportName); + } else if (context.getDevices().size() > 1) { + ParallelDeviceExecutor<Boolean> executor = + new ParallelDeviceExecutor<>(context.getDevices().size()); + List<Callable<Boolean>> callableTasks = new ArrayList<>(); + final String reportName = bugreportName; + for (ITestDevice device : context.getDevices()) { + Callable<Boolean> callableTask = + () -> { + takeBugreport(device, listener, reportName); + return true; + }; + callableTasks.add(callableTask); + } + // Capture the bugreports best effort, ignore the results. + executor.invokeAll(callableTasks, 5, TimeUnit.MINUTES); } - // Capture the bugreports best effort, ignore the results. - executor.invokeAll(callableTasks, 5, TimeUnit.MINUTES); } } // Save the device executeShellCommand logs @@ -384,6 +387,10 @@ public class TestInvocation implements ITestInvocation { mStopCause); FailureDescription failure = FailureDescription.create(message, FailureStatus.CANCELLED); + failure.setErrorIdentifier(InfraErrorIdentifier.INVOCATION_CANCELLED); + failure.setCause( + new HarnessRuntimeException( + message, InfraErrorIdentifier.INVOCATION_CANCELLED)); reportFailure(failure, listener); PrettyPrintDelimiter.printStageDelimiter(message); if (mStopRequestTime != null) { @@ -399,22 +406,7 @@ public class TestInvocation implements ITestInvocation { Runtime.getRuntime().removeShutdownHook(reportThread); elapsedTime = System.currentTimeMillis() - startTime; - if (!resumed) { - // Init a log for the end of the host_log. - ILeveledLogOutput endHostLog = config.getLogOutput(); - endHostLog.init(); - getLogRegistry().registerLogger(endHostLog); - PrettyPrintDelimiter.printStageDelimiter("===== Result Reporters ====="); - try { - // Copy the invocation metrics to the context - ((InvocationContext) context).logInvocationMetrics(); - listener.invocationEnded(elapsedTime); - } finally { - InvocationMetricLogger.clearInvocationMetrics(); - endHostLog.closeLog(); - getLogRegistry().unregisterLogger(); - } - } + reportInvocationEnded(config, context, listener, elapsedTime); } finally { TfObjectTracker.clearTracking(); CurrentInvocation.clearInvocationInfos(); @@ -498,7 +490,7 @@ public class TestInvocation implements ITestInvocation { private void reportHostLog(ITestInvocationListener listener, IConfiguration config) { String name = TRADEFED_LOG_NAME; - if (config.getConfigurationObject(TradefedDelegator.DELEGATE_OBJECT) != null) { + if (mDelegatedInvocation) { name = TRADEFED_DELEGATED_LOG_NAME; } reportHostLog(listener, config, name); @@ -650,7 +642,7 @@ public class TestInvocation implements ITestInvocation { invocationPath.reportLogs(device, listener, Stage.ERROR); } reportHostLog(listener, config); - listener.invocationEnded(0L); + reportInvocationEnded(config, testInfo.getContext(), listener, 0L); return false; } @@ -703,7 +695,7 @@ public class TestInvocation implements ITestInvocation { invocationPath.reportLogs(device, listener, Stage.ERROR); } reportHostLog(listener, config); - listener.invocationEnded(0L); + reportInvocationEnded(config, context, listener, 0L); return false; } } @@ -797,6 +789,7 @@ public class TestInvocation implements ITestInvocation { mode = RunMode.REMOTE_INVOCATION; } if (config.getConfigurationObject(TradefedDelegator.DELEGATE_OBJECT) != null) { + mDelegatedInvocation = true; mode = RunMode.DELEGATED_INVOCATION; } IInvocationExecution invocationPath = createInvocationExec(mode); @@ -1156,6 +1149,35 @@ public class TestInvocation implements ITestInvocation { return devicesStates; } + private void reportInvocationEnded( + IConfiguration config, + IInvocationContext context, + ITestInvocationListener listener, + long elapsedTime) { + // Init a log for the end of the host_log. + ILeveledLogOutput endHostLog = config.getLogOutput(); + try { + endHostLog.init(); + getLogRegistry().registerLogger(endHostLog); + } catch (IOException e) { + CLog.e(e); + endHostLog = null; + } + + PrettyPrintDelimiter.printStageDelimiter("===== Result Reporters ====="); + try { + // Copy the invocation metrics to the context + ((InvocationContext) context).logInvocationMetrics(); + listener.invocationEnded(elapsedTime); + } finally { + InvocationMetricLogger.clearInvocationMetrics(); + if (endHostLog != null) { + endHostLog.closeLog(); + getLogRegistry().unregisterLogger(); + } + } + } + /** Helper Thread that ensures host_log is reported in case of killed JVM */ private class ReportHostLog extends Thread { diff --git a/src/com/android/tradefed/invoker/shard/StrictShardHelper.java b/src/com/android/tradefed/invoker/shard/StrictShardHelper.java index f3aeffc64..cf30b05f9 100644 --- a/src/com/android/tradefed/invoker/shard/StrictShardHelper.java +++ b/src/com/android/tradefed/invoker/shard/StrictShardHelper.java @@ -35,7 +35,10 @@ import com.android.tradefed.util.TimeUtil; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** Sharding strategy to create strict shards that do not report together, */ public class StrictShardHelper extends ShardHelper { @@ -49,6 +52,7 @@ public class StrictShardHelper extends ShardHelper { ITestLogger logger) { Integer shardCount = config.getCommandOptions().getShardCount(); Integer shardIndex = config.getCommandOptions().getShardIndex(); + boolean optimizeMainline = config.getCommandOptions().getOptimizeMainlineTest(); if (shardIndex == null) { return super.shardConfig(config, testInfo, rescheduler, logger); @@ -69,11 +73,50 @@ public class StrictShardHelper extends ShardHelper { splitList = splitTests(listAllTests, shardCount).get(shardIndex); } aggregateSuiteModules(splitList); + if (optimizeMainline) { + CLog.i("Reordering the test modules list for index: %s", shardIndex); + reorderTestModules(splitList); + } config.setTests(splitList); return false; } /** + * Helper to re order the list full list of {@link IRemoteTest} for mainline. + * + * @param tests the {@link IRemoteTest} containing all the tests that need to run. + */ + private void reorderTestModules(List<IRemoteTest> tests) { + Collections.sort(tests, new Comparator<IRemoteTest>() { + @Override + public int compare(IRemoteTest o1, IRemoteTest o2) { + String moduleId1 = ((ITestSuite)o1).getDirectModule().getId(); + String moduleId2 = ((ITestSuite)o2).getDirectModule().getId(); + return getMainlineId(moduleId1).compareTo(getMainlineId(moduleId2)); + } + }); + } + + /** + * Returns the parameterized mainline modules' name defined in the square brackets. + * + * @param id The module's name. + * @throws RuntimeException if the module name doesn't match the pattern for mainline modules. + */ + private String getMainlineId(String id) { + // Pattern used to identify the parameterized mainline modules defined in the square + // brackets. + Pattern parameterizedMainlineRegex = Pattern.compile("\\[(.*(\\.apk|.apex|.apks))\\]$"); + Matcher m = parameterizedMainlineRegex.matcher(id); + if (m.find()) { + return m.group(1); + } + throw new RuntimeException( + String.format("Module: %s doesn't match the pattern for mainline modules. The " + + "pattern should end with apk/apex/apks.", id)); + } + + /** * Helper to return the full list of {@link IRemoteTest} based on {@link IShardableTest} split. * * @param config the {@link IConfiguration} describing the invocation. diff --git a/src/com/android/tradefed/monitoring/LabResourceDeviceMonitor.java b/src/com/android/tradefed/monitoring/LabResourceDeviceMonitor.java new file mode 100644 index 000000000..8366e0e52 --- /dev/null +++ b/src/com/android/tradefed/monitoring/LabResourceDeviceMonitor.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.monitoring; + +import com.android.loganalysis.util.config.OptionClass; +import com.android.tradefed.device.DeviceAllocationState; +import com.android.tradefed.device.IDeviceMonitor; +import com.android.tradefed.log.LogUtil; + +import com.google.common.annotations.VisibleForTesting; +import com.google.dualhomelab.monitoringagent.resourcemonitoring.LabResource; +import com.google.dualhomelab.monitoringagent.resourcemonitoring.LabResourceRequest; +import com.google.dualhomelab.monitoringagent.resourcemonitoring.LabResourceServiceGrpc; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Optional; +import java.util.concurrent.Executors; + +import io.grpc.Server; +import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; +import io.grpc.stub.StreamObserver; + +/** The lab resource monitor which initializes/manages the gRPC server for LabResourceService. */ +@OptionClass(alias = "lab-resource-monitor") +public class LabResourceDeviceMonitor extends LabResourceServiceGrpc.LabResourceServiceImplBase + implements IDeviceMonitor { + public static final String SERVER_HOSTNAME = "localhost"; + public static final int DEFAULT_PORT = 8887; + public static final int DEFAULT_THREAD_COUNT = 1; + private Optional<Server> mServer = Optional.empty(); + + @VisibleForTesting + Optional<Server> getServer() { + return mServer; + } + + /** {@inheritDoc} */ + @Override + public void run() { + if (!mServer.isPresent()) { + mServer = + Optional.of( + NettyServerBuilder.forAddress( + new InetSocketAddress(SERVER_HOSTNAME, DEFAULT_PORT)) + .addService(this) + .executor(Executors.newFixedThreadPool(DEFAULT_THREAD_COUNT)) + .build()); + try { + mServer.get().start(); + } catch (IOException e) { + LogUtil.CLog.e(e); + } + } + } + + /** {@inheritDoc} */ + @Override + public void stop() { + mServer.ifPresent(Server::shutdown); + } + + /** {@inheritDoc} */ + @Override + public void setDeviceLister(DeviceLister lister) { + // Ignore + } + + /** {@inheritDoc} */ + @Override + public void notifyDeviceStateChange( + String serial, DeviceAllocationState oldState, DeviceAllocationState newState) { + // Ignore + } + + /** The gRPC request handler. */ + @Override + public void getLabResource( + LabResourceRequest request, StreamObserver<LabResource> responseObserver) { + super.getLabResource(request, responseObserver); + } +} diff --git a/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java b/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java index 8833ed5c3..4a934d801 100644 --- a/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java +++ b/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java @@ -24,6 +24,7 @@ import com.android.tradefed.config.OptionClass; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; import com.android.tradefed.util.MultiMap; +import com.android.tradefed.util.ProtoUtil; import com.android.tradefed.util.proto.TfMetricProtoUtil; import com.google.protobuf.Descriptors.FieldDescriptor; @@ -148,48 +149,6 @@ public class StatsdEventMetricPostProcessor extends StatsdGenericPostProcessor { return metrics; } - /** - * Get a nested field reference, i.e. field_1.field_2.field_3, from a proto message as a string. - * Returns an empty list when a field cannot be found, either because it's invalid or does not - * exist in the message. - * - * <p>If the field reference contains repeated fields, each instance is expanded, resulting in a - * list of strings. - */ - private List<String> getNestedFieldFromMessageAsStrings( - Object messageOrObject, List<String> references) { - if (references.isEmpty()) { - return Arrays.asList(String.valueOf(messageOrObject)); - } - if (!(messageOrObject instanceof Message)) { - CLog.e( - "Attempting to read field %s from object of type %s, " - + "which is not a proto message.", - references.get(0), messageOrObject.getClass()); - return new ArrayList<String>(); - } - Message message = (Message) messageOrObject; - String reference = references.get(0); - FieldDescriptor fieldDescriptor = message.getDescriptorForType().findFieldByName(reference); - if (fieldDescriptor == null) { - CLog.e("Could not find field %s in message %s.", reference, message); - return new ArrayList<String>(); - } - Object fieldValue = message.getField(fieldDescriptor); - if (fieldValue instanceof List) { - return ((List<? extends Object>) fieldValue) - .stream() - .flatMap( - v -> - getNestedFieldFromMessageAsStrings( - v, references.subList(1, references.size())) - .stream()) - .collect(Collectors.toList()); - } - return getNestedFieldFromMessageAsStrings( - fieldValue, references.subList(1, references.size())); - } - /** Fill in the placeholders in the formatter using the proto message as source. */ private List<String> fillInPlaceholders( String formatter, EventMetricData eventMetric, Message atomContent) { @@ -202,12 +161,12 @@ public class StatsdEventMetricPostProcessor extends StatsdGenericPostProcessor { List<String> actual = new ArrayList(); if (fieldReference.startsWith("_")) { actual.addAll( - getNestedFieldFromMessageAsStrings( + ProtoUtil.getNestedFieldFromMessageAsStrings( eventMetric, Arrays.asList(fieldReference.substring(1).split("\\.")))); } else { actual.addAll( - getNestedFieldFromMessageAsStrings( + ProtoUtil.getNestedFieldFromMessageAsStrings( atomContent, Arrays.asList(fieldReference.split("\\.")))); } // If both the existing expansion results and newly expanded results have multiple diff --git a/src/com/android/tradefed/result/CollectingTestListener.java b/src/com/android/tradefed/result/CollectingTestListener.java index d9ec6ec1e..a51d2ae23 100644 --- a/src/com/android/tradefed/result/CollectingTestListener.java +++ b/src/com/android/tradefed/result/CollectingTestListener.java @@ -311,6 +311,12 @@ public class CollectingTestListener implements ITestInvocationListener, ILogSave } @Override + public void testAssumptionFailure(TestDescription test, FailureDescription failure) { + setCountDirty(); + mCurrentTestRunResult.testAssumptionFailure(test, failure); + } + + @Override public void testIgnored(TestDescription test) { setCountDirty(); mCurrentTestRunResult.testIgnored(test); diff --git a/src/com/android/tradefed/result/JsonHttpTestResultReporter.java b/src/com/android/tradefed/result/JsonHttpTestResultReporter.java index b89e4b036..06c9585a5 100644 --- a/src/com/android/tradefed/result/JsonHttpTestResultReporter.java +++ b/src/com/android/tradefed/result/JsonHttpTestResultReporter.java @@ -36,6 +36,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -207,7 +208,8 @@ public class JsonHttpTestResultReporter extends CollectingTestListener { // Parse run metrics if (runResult.getRunMetrics().size() > 0) { - JSONObject runResultMetrics = new JSONObject(runResult.getRunMetrics()); + JSONObject runResultMetrics = new JSONObject( + getValidMetrics(runResult.getRunMetrics())); String reportingUnit = runResult.getName(); if (mReportingUnitKeySuffix != null && !mReportingUnitKeySuffix.isEmpty()) { reportingUnit += mReportingUnitKeySuffix; @@ -233,7 +235,8 @@ public class JsonHttpTestResultReporter extends CollectingTestListener { } resultsName.append(String.format("%s%s", reportingUnit, RESULT_SEPARATOR)); if (testResult.getMetrics().size() > 0) { - JSONObject testResultMetrics = new JSONObject(testResult.getMetrics()); + JSONObject testResultMetrics = new JSONObject( + getValidMetrics(testResult.getMetrics())); allTestMetrics.put(reportingUnit, testResultMetrics); } } @@ -259,4 +262,23 @@ public class JsonHttpTestResultReporter extends CollectingTestListener { return result; } -} + + /** + * Add only the numeric metrics and skip posting the non-numeric metrics. + * + * @param collectedMetrics contains all the metrics. + * @return only the numeric metrics. + */ + public Map<String, String> getValidMetrics(Map<String, String> collectedMetrics) { + Map<String, String> validMetrics = new HashMap<>(); + for (Map.Entry<String, String> entry : collectedMetrics.entrySet()) { + try { + Double.parseDouble(entry.getValue()); + validMetrics.put(entry.getKey(), entry.getValue()); + } catch (Exception e) { + // Skip adding the non numeric metric. + } + } + return validMetrics; + } +}
\ No newline at end of file diff --git a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java index 8891069f9..a14cb8aee 100644 --- a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java +++ b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java @@ -19,6 +19,8 @@ import com.android.loganalysis.item.JavaCrashItem; import com.android.loganalysis.item.LogcatItem; import com.android.loganalysis.parser.LogcatParser; import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.invoker.logger.InvocationMetricLogger; +import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; import com.android.tradefed.result.error.DeviceErrorIdentifier; @@ -41,6 +43,11 @@ public class LogcatCrashResultForwarder extends ResultForwarder { /** Special error message from the instrumentation when something goes wrong on device side. */ public static final String ERROR_MESSAGE = "Process crashed."; public static final String SYSTEM_CRASH_MESSAGE = "System has crashed."; + public static final String TIMEOUT_MESSAGES[] = { + "Failed to receive adb shell test output", + "TimeoutException when running tests", + "TestTimedOutException: test timed out after", + }; public static final int MAX_NUMBER_CRASH = 3; @@ -76,8 +83,16 @@ public class LogcatCrashResultForwarder extends ResultForwarder { if (trace.compareTo(failure.getErrorMessage()) != 0) { // Crash stack trace found, consider this a test failure. failure.setFailureStatus(FailureStatus.TEST_FAILURE); + } else if (isTimeout(failure.getErrorMessage())) { + failure.setFailureStatus(FailureStatus.TIMED_OUT); } failure.setErrorMessage(trace); + // Add metrics for assessing uncaught IntrumentationTest crash failures (test level). + InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.TEST_CRASH_FAILURES, 1); + if (FailureStatus.UNSET.equals(failure.getFailureStatus())) { + InvocationMetricLogger.addInvocationMetrics( + InvocationMetricKey.UNCAUGHT_TEST_CRASH_FAILURES, 1); + } super.testFailed(test, failure); } @@ -109,6 +124,12 @@ public class LogcatCrashResultForwarder extends ResultForwarder { if (isCrash(errorMessage)) { error.setErrorIdentifier(DeviceErrorIdentifier.INSTRUMENTATION_CRASH); } + // Add metrics for assessing uncaught IntrumentationTest crash failures. + InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.CRASH_FAILURES, 1); + if (FailureStatus.UNSET.equals(error.getFailureStatus())) { + InvocationMetricLogger.addInvocationMetrics( + InvocationMetricKey.UNCAUGHT_CRASH_FAILURES, 1); + } super.testRunFailed(error); } @@ -131,6 +152,14 @@ public class LogcatCrashResultForwarder extends ResultForwarder { return errorMessage.contains(ERROR_MESSAGE) || errorMessage.contains(SYSTEM_CRASH_MESSAGE); } + private boolean isTimeout(String errorMessage) { + for (String timeoutMessage : TIMEOUT_MESSAGES) { + if (errorMessage.contains(timeoutMessage)) { + return true; + } + } + return false; + } /** * Extract a formatted object from the logcat snippet. * diff --git a/src/com/android/tradefed/result/ResultForwarder.java b/src/com/android/tradefed/result/ResultForwarder.java index ba38e54ca..c65140522 100644 --- a/src/com/android/tradefed/result/ResultForwarder.java +++ b/src/com/android/tradefed/result/ResultForwarder.java @@ -341,6 +341,20 @@ public class ResultForwarder implements ITestInvocationListener { } @Override + public void testAssumptionFailure(TestDescription test, FailureDescription failure) { + for (ITestInvocationListener listener : mListeners) { + try { + listener.testAssumptionFailure(test, failure); + } catch (RuntimeException e) { + CLog.e( + "Exception while invoking %s#testAssumptionFailure", + listener.getClass().getName()); + CLog.e(e); + } + } + } + + @Override public void testIgnored(TestDescription test) { for (ITestInvocationListener listener : mListeners) { try { diff --git a/src/com/android/tradefed/result/suite/SuiteResultReporter.java b/src/com/android/tradefed/result/suite/SuiteResultReporter.java index 424cd9d44..82e46855c 100644 --- a/src/com/android/tradefed/result/suite/SuiteResultReporter.java +++ b/src/com/android/tradefed/result/suite/SuiteResultReporter.java @@ -401,6 +401,9 @@ public class SuiteResultReporter extends CollectingTestListener { @Override public TestSummary getSummary() { + if (mSummary == null || mSummary.toString().isEmpty()) { + return null; + } TestSummary summary = new TestSummary(new TypedString(mSummary.toString(), Type.TEXT)); summary.setSource(SUITE_REPORTER_SOURCE); return summary; diff --git a/src/com/android/tradefed/retry/BaseRetryDecision.java b/src/com/android/tradefed/retry/BaseRetryDecision.java index 0228ae9a5..599aed8d4 100644 --- a/src/com/android/tradefed/retry/BaseRetryDecision.java +++ b/src/com/android/tradefed/retry/BaseRetryDecision.java @@ -20,14 +20,20 @@ import com.android.tradefed.config.Option; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.device.StubDevice; +import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice; import com.android.tradefed.invoker.IInvocationContext; +import com.android.tradefed.invoker.logger.InvocationMetricLogger; +import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.result.TestDescription; import com.android.tradefed.result.TestResult; import com.android.tradefed.result.TestRunResult; +import com.android.tradefed.result.error.DeviceErrorIdentifier; +import com.android.tradefed.targetprep.TargetSetupError; import com.android.tradefed.testtype.IRemoteTest; import com.android.tradefed.testtype.ITestFilterReceiver; import com.android.tradefed.testtype.retry.IAutoRetriableTest; +import com.android.tradefed.testtype.suite.ModuleDefinition; import java.util.ArrayList; import java.util.HashSet; @@ -53,6 +59,13 @@ public class BaseRetryDecision implements IRetryDecision { private boolean mRebootAtLastRetry = false; @Option( + name = "reset-at-last-retry", + description = + "Reset or powerwash the device at the last retry attempt. If this option is " + + "set, option `reboot-at-last-retry` will be ignored.") + private boolean mResetAtLastRetry = false; + + @Option( name = "max-testcase-run-count", description = "If the IRemoteTest can have its testcases run multiple times, " @@ -113,6 +126,16 @@ public class BaseRetryDecision implements IRetryDecision { public boolean shouldRetry( IRemoteTest test, int attemptJustExecuted, List<TestRunResult> previousResults) throws DeviceNotAvailableException { + return shouldRetry(test, null, attemptJustExecuted, previousResults); + } + + @Override + public boolean shouldRetry( + IRemoteTest test, + ModuleDefinition module, + int attemptJustExecuted, + List<TestRunResult> previousResults) + throws DeviceNotAvailableException { // Keep track of some results for the test in progress for statistics purpose. if (test != mCurrentlyConsideredTest) { mCurrentlyConsideredTest = test; @@ -143,7 +166,7 @@ public class BaseRetryDecision implements IRetryDecision { boolean shouldRetry = handleRetryFailures(filterableTest, previousResults); if (shouldRetry) { // In case of retry, go through the recovery routine - recoverStateOfDevices(getDevices(), attemptJustExecuted); + recoverStateOfDevices(getDevices(), attemptJustExecuted, module); } return shouldRetry; } else if (test instanceof IAutoRetriableTest) { @@ -295,13 +318,74 @@ public class BaseRetryDecision implements IRetryDecision { } /** Recovery attempt on the device to get it a better state before next retry. */ - private void recoverStateOfDevices(List<ITestDevice> devices, int lastAttempt) + private void recoverStateOfDevices( + List<ITestDevice> devices, int lastAttempt, ModuleDefinition module) throws DeviceNotAvailableException { + if (lastAttempt == (mMaxRetryAttempts - 2)) { + if (mResetAtLastRetry) { + resetDevice(module, devices); + } else if (mRebootAtLastRetry) { + for (ITestDevice device : devices) { + device.reboot(); + continue; + } + } + } + } + + private void resetDevice(ModuleDefinition module, List<ITestDevice> devices) + throws DeviceNotAvailableException { + CLog.d("Reset devices..."); + int deviceResetCount = 0; for (ITestDevice device : devices) { - if (mRebootAtLastRetry && (lastAttempt == (mMaxRetryAttempts - 2))) { - device.reboot(); + if (!(device instanceof RemoteAndroidVirtualDevice)) { + CLog.i( + "Device %s of type %s does not support powerwash.", + device.getSerialNumber(), device.getClass()); continue; } + boolean success = false; + try { + success = ((RemoteAndroidVirtualDevice) device).powerwashGce(); + deviceResetCount++; + } catch (TargetSetupError e) { + CLog.e(e); + throw new DeviceNotAvailableException( + String.format( + "Failed to powerwash device: %s\nError: %s", + device.getSerialNumber(), e.toString()), + e, + device.getSerialNumber(), + DeviceErrorIdentifier.DEVICE_FAILED_TO_RESET); + } + + if (!success) { + throw new DeviceNotAvailableException( + String.format("Failed to powerwash device: %s", device.getSerialNumber()), + device.getSerialNumber(), + DeviceErrorIdentifier.DEVICE_FAILED_TO_RESET); + } + } + + if (module != null) { + InvocationMetricLogger.addInvocationMetrics( + InvocationMetricKey.DEVICE_RESET_MODULES, module.getId()); + InvocationMetricLogger.addInvocationMetrics( + InvocationMetricKey.DEVICE_RESET_COUNT, deviceResetCount); + + // Run all preparers including suite level ones. + Throwable preparationException = + module.runPreparation(true /* includeSuitePreparers */); + if (preparationException != null) { + CLog.e(preparationException); + throw new DeviceNotAvailableException( + String.format( + "Failed to reset devices before retry: %s", + preparationException.toString()), + preparationException, + devices.get(0).getSerialNumber(), + DeviceErrorIdentifier.DEVICE_FAILED_TO_RESET); + } } } } diff --git a/src/com/android/tradefed/retry/IRetryDecision.java b/src/com/android/tradefed/retry/IRetryDecision.java index 70b3e0039..bc0facef4 100644 --- a/src/com/android/tradefed/retry/IRetryDecision.java +++ b/src/com/android/tradefed/retry/IRetryDecision.java @@ -19,6 +19,7 @@ import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.invoker.IInvocationContext; import com.android.tradefed.result.TestRunResult; import com.android.tradefed.testtype.IRemoteTest; +import com.android.tradefed.testtype.suite.ModuleDefinition; import java.util.List; @@ -58,6 +59,24 @@ public interface IRetryDecision { throws DeviceNotAvailableException; /** + * Decide whether or not retry should be attempted. Also make any necessary changes to the + * {@link IRemoteTest} to be retried (Applying filters, etc.). + * + * @param test The {@link IRemoteTest} that just ran. + * @param module The {@link ModuleDefinition} object for the test module. + * @param attemptJustExecuted The number of the attempt that we just ran. + * @param previousResults The list of {@link TestRunResult} of the test that just ran. + * @return True if we should retry, False otherwise. + * @throws DeviceNotAvailableException Can be thrown during device recovery + */ + public boolean shouldRetry( + IRemoteTest test, + ModuleDefinition module, + int attemptJustExecuted, + List<TestRunResult> previousResults) + throws DeviceNotAvailableException; + + /** * {@link #shouldRetry(IRemoteTest, int, List)} will most likely be called before the last retry * attempt, so we might be missing the very last attempt results for statistics purpose. This * method allows those results to be provided for proper statistics calculations. diff --git a/src/com/android/tradefed/retry/ResultAggregator.java b/src/com/android/tradefed/retry/ResultAggregator.java index 43848fda7..b028bdfe9 100644 --- a/src/com/android/tradefed/retry/ResultAggregator.java +++ b/src/com/android/tradefed/retry/ResultAggregator.java @@ -257,6 +257,12 @@ public class ResultAggregator extends CollectingTestListener { } @Override + public void testAssumptionFailure(TestDescription test, FailureDescription failure) { + super.testAssumptionFailure(test, failure); + mDetailedForwarder.testAssumptionFailure(test, failure); + } + + @Override public void testFailed(TestDescription test, String trace) { super.testFailed(test, trace); mDetailedForwarder.testFailed(test, trace); diff --git a/src/com/android/tradefed/sandbox/ISandbox.java b/src/com/android/tradefed/sandbox/ISandbox.java index c78c1a9b8..ba0fc67f9 100644 --- a/src/com/android/tradefed/sandbox/ISandbox.java +++ b/src/com/android/tradefed/sandbox/ISandbox.java @@ -65,7 +65,7 @@ public interface ISandbox { */ public File getTradefedSandboxEnvironment( IInvocationContext context, IConfiguration nonVersionedConfig, String[] args) - throws ConfigurationException; + throws Exception; /** * Create a classpath based on the environment and the working directory returned by {@link diff --git a/src/com/android/tradefed/sandbox/TradefedSandbox.java b/src/com/android/tradefed/sandbox/TradefedSandbox.java index 0659c7c95..942955d88 100644 --- a/src/com/android/tradefed/sandbox/TradefedSandbox.java +++ b/src/com/android/tradefed/sandbox/TradefedSandbox.java @@ -256,7 +256,7 @@ public class TradefedSandbox implements ISandbox { config.getCommandLine(), /** no logging */ false)); - } catch (ConfigurationException e) { + } catch (Exception e) { return e; } @@ -293,7 +293,7 @@ public class TradefedSandbox implements ISandbox { @Override public File getTradefedSandboxEnvironment( IInvocationContext context, IConfiguration nonVersionedConfig, String[] args) - throws ConfigurationException { + throws Exception { SandboxOptions options = getSandboxOptions(nonVersionedConfig); // Check that we have no args conflicts. if (options.getSandboxTfDirectory() != null && options.getSandboxBuildId() != null) { diff --git a/src/com/android/tradefed/sandbox/TradefedSandboxRunner.java b/src/com/android/tradefed/sandbox/TradefedSandboxRunner.java index 69feed334..c26804bda 100644 --- a/src/com/android/tradefed/sandbox/TradefedSandboxRunner.java +++ b/src/com/android/tradefed/sandbox/TradefedSandboxRunner.java @@ -125,6 +125,8 @@ public class TradefedSandboxRunner { initGlobalConfig(new String[] {}); mScheduler = getCommandScheduler(); mScheduler.start(); + // Wait 2 secs to let device discovery finish + RunUtil.getDefault().sleep(2000); mScheduler.execCommand( context, new StubScheduledInvocationListener(), argList.toArray(new String[0])); } catch (NoDeviceException e) { diff --git a/src/com/android/tradefed/targetprep/DeviceSetup.java b/src/com/android/tradefed/targetprep/DeviceSetup.java index 2db82ba09..9c8a83f94 100644 --- a/src/com/android/tradefed/targetprep/DeviceSetup.java +++ b/src/com/android/tradefed/targetprep/DeviceSetup.java @@ -894,7 +894,7 @@ public class DeviceSetup extends BaseTargetPreparer { CLog.d("Skipping connect wifi due to force-skip-run-commands"); return; } - if (mWifiSsid == null && mWifiSsidToPsk.isEmpty()) { + if ((mWifiSsid == null || mWifiSsid.isEmpty()) && mWifiSsidToPsk.isEmpty()) { return; } diff --git a/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java b/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java index 1b9299be7..d896f006e 100644 --- a/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java +++ b/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java @@ -15,7 +15,7 @@ */ package com.android.tradefed.targetprep; -import com.android.tradefed.build.IDeviceBuildInfo; +import com.android.tradefed.build.IBuildInfo; import com.android.tradefed.config.GlobalConfiguration; import com.android.tradefed.config.Option; import com.android.tradefed.config.OptionClass; @@ -80,7 +80,7 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer { public void setUp(TestInformation testInfo) throws TargetSetupError, BuildError, DeviceNotAvailableException { ITestDevice device = testInfo.getDevice(); - IDeviceBuildInfo buildInfo = (IDeviceBuildInfo) testInfo.getBuildInfo(); + IBuildInfo buildInfo = testInfo.getBuildInfo(); File tmpDir = null; try { @@ -138,10 +138,10 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer { * Flash GKI images. * * @param device the {@link ITestDevice} - * @param buildInfo the {@link IDeviceBuildInfo} the device build info + * @param buildInfo the {@link IBuildInfo} the build info * @throws TargetSetupError, DeviceNotAvailableException, IOException */ - private void flashGki(ITestDevice device, IDeviceBuildInfo buildInfo) + private void flashGki(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, DeviceNotAvailableException { IDeviceManager deviceManager = getDeviceManager(); device.waitForDeviceOnline(); @@ -180,13 +180,13 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer { * Validate GKI boot image is expected. (Obsoleted. Please call with tmpDir provided) * * @param device the {@link ITestDevice} - * @param buildInfo the {@link IDeviceBuildInfo} the device build info + * @param buildInfo the {@link IBuildInfo} the build info * @throws TargetSetupError if there is no valid gki boot.img */ - public void validateGkiBootImg(ITestDevice device, IDeviceBuildInfo buildInfo) + public void validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError { throw new TargetSetupError( - "Obsoleted. Please use validateGkiBootImg(ITestDevice, IDeviceBuildInfo, File)", + "Obsoleted. Please use validateGkiBootImg(ITestDevice, IBuildInfo, File)", device.getDeviceDescriptor()); } @@ -194,12 +194,12 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer { * Validate GKI boot image is expected. Throw exception if there is no valid boot.img. * * @param device the {@link ITestDevice} - * @param buildInfo the {@link IDeviceBuildInfo} the device build info + * @param buildInfo the {@link IBuildInfo} the build info * @param tmpDir the temporary directory {@link File} * @throws TargetSetupError if there is no valid gki boot.img */ @VisibleForTesting - protected void validateGkiBootImg(ITestDevice device, IDeviceBuildInfo buildInfo, File tmpDir) + protected void validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir) throws TargetSetupError { if (buildInfo.getFile(GKI_BOOT_IMG) != null && mBootImageFileName != null) { mBootImg = @@ -306,7 +306,10 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer { ZipUtil2.extractZip(sourceFile, destDir); requestedFile = FileUtil.findFile(destDir, requestedFileName); } catch (IOException e) { - throw new TargetSetupError(e.getMessage(), e, device.getDeviceDescriptor()); + throw new TargetSetupError( + String.format("Fail to get %s from %s", requestedFileName, sourceFile), + e, + device.getDeviceDescriptor()); } } else if (sourceFile.isDirectory()) { requestedFile = FileUtil.findFile(sourceFile, requestedFileName); @@ -349,7 +352,7 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer { throw new TargetSetupError( String.format( "fastboot command %s failed in device %s. stdout: %s, stderr: %s", - cmdArgs[0], + cmdArgs, device.getSerialNumber(), result.getStdout(), result.getStderr()), diff --git a/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java b/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java index b971a4a73..25bb99f77 100644 --- a/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java +++ b/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java @@ -15,7 +15,7 @@ */ package com.android.tradefed.targetprep; -import com.android.tradefed.build.IDeviceBuildInfo; +import com.android.tradefed.build.IBuildInfo; import com.android.tradefed.config.GlobalConfiguration; import com.android.tradefed.config.Option; import com.android.tradefed.config.OptionClass; @@ -49,9 +49,6 @@ import java.util.regex.Pattern; @OptionClass(alias = "gsi-device-flash-preparer") public class GsiDeviceFlashPreparer extends BaseTargetPreparer { - private static final String GSI_SYSTEM_IMG = "gsi_system.img"; - private static final String GSI_VBMETA_IMG = "gsi_vbmeta.img"; - private static final String GKI_BOOT_IMG = "gki_boot.img"; private static final int DYNAMIC_PARTITION_API_LEVEL = 29; // Wait time for device state to stablize in millisecond private static final int STATE_STABLIZATION_WAIT_TIME_MLLISECS = 60000; @@ -63,26 +60,40 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer { private long mDeviceBootTime = 5 * 60 * 1000; @Option( + name = "system-image-zip-name", + description = "The name of the zip file containing the system image in BuildInfo.") + private String mSystemImageZipName = "gsi_system.img"; + + @Option( name = "system-image-file-name", description = - "The system image file name to search for if provided gsi_system.img is in " - + "a zip file or directory.") + "The system image file name to search for if provided system image " + + "is in a zip file or directory.") private String mSystemImageFileName = "system.img"; @Option( + name = "vbmeta-image-zip-name", + description = "The name of the zip file containing the system image in BuildInfo.") + private String mVbmetaImageZipName = "gsi_vbmeta.img"; + + @Option( name = "vbmeta-image-file-name", description = - "The vbmeta image file name to search for if provided gsi_vbmeta.img is in " - + "a zip file or directory.") + "The vbmeta image file name to search for if provided vbmeta image is " + + "in a zip file or directory.") private String mVbmetaImageFileName = "vbmeta.img"; @Option( + name = "boot-image-zip-name", + description = "The name of the zip file containing the boot image in BuildInfo.") + private String mBootImageZipName = "gki_boot.img"; + + @Option( name = "boot-image-file-name", description = - "The boot image file name to search for if gki_boot.img is provided in BuildInfo and the provided " - + "file is a zip file or directory, for example boot-5.4.img. By default when gki_boot.img " - + "is provided in BuildInfo with a zip file or file directory, the target preparer will use" - + " the first found file that matches boot(.*).img as file name.") + "The boot image file name to search for if boot image is is in a zip " + + "file or directory, for example boot-5.4.img. The first file" + + "match the provided name string will be used.") private String mBootImageFileName = "boot(.*).img"; @Option( @@ -99,7 +110,7 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer { public void setUp(TestInformation testInfo) throws TargetSetupError, BuildError, DeviceNotAvailableException { ITestDevice device = testInfo.getDevice(); - IDeviceBuildInfo buildInfo = (IDeviceBuildInfo) testInfo.getBuildInfo(); + IBuildInfo buildInfo = testInfo.getBuildInfo(); File tmpDir = null; try { @@ -157,10 +168,10 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer { * Flash GSI images. * * @param device the {@link ITestDevice} - * @param buildInfo the {@link IDeviceBuildInfo} the device build info + * @param buildInfo the {@link IBuildInfo} the build info * @throws TargetSetupError, DeviceNotAvailableException, IOException */ - private void flashGsi(ITestDevice device, IDeviceBuildInfo buildInfo) + private void flashGsi(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, DeviceNotAvailableException { IDeviceManager deviceManager = getDeviceManager(); device.waitForDeviceOnline(); @@ -198,8 +209,8 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer { if (shouldUseFastbootd) { device.rebootIntoFastbootd(); if (mShouldEraseProductPartition) { - executeFastbootCmd( - device, "delete-logical-partition", "product" + currSlot); + device.executeLongFastbootCommand( + "delete-logical-partition", "product" + currSlot); } } executeFastbootCmd(device, "erase", "system" + currSlot); @@ -224,33 +235,38 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer { * Validate GSI image is expected. Throw exception if there is no valid GSI image. * * @param device the {@link ITestDevice} - * @param buildInfo the {@link IDeviceBuildInfo} the device build info + * @param buildInfo the {@link IBuildInfo} the build info * @param tmpDir the temporary directory {@link File} * @throws TargetSetupError if there is no valid gki boot.img */ - private void validateGsiImg(ITestDevice device, IDeviceBuildInfo buildInfo, File tmpDir) + private void validateGsiImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir) throws TargetSetupError { - if (buildInfo.getFile(GSI_SYSTEM_IMG) == null) { - throw new TargetSetupError( - String.format("BuildInfo doesn't contain file key %s.", GSI_SYSTEM_IMG), - device.getDeviceDescriptor()); - } - if (buildInfo.getFile(GSI_VBMETA_IMG) == null) { + if (buildInfo.getFile(mSystemImageZipName) == null) { throw new TargetSetupError( - String.format("BuildInfo doesn't contain file key %s.", GSI_VBMETA_IMG), + String.format("BuildInfo doesn't contain file key %s.", mSystemImageZipName), device.getDeviceDescriptor()); } mSystemImg = getRequestedFile( - device, mSystemImageFileName, buildInfo.getFile(GSI_SYSTEM_IMG), tmpDir); - mVbmetaImg = - getRequestedFile( - device, mVbmetaImageFileName, buildInfo.getFile(GSI_VBMETA_IMG), tmpDir); - - if (buildInfo.getFile(GKI_BOOT_IMG) != null && mBootImageFileName != null) { + device, + mSystemImageFileName, + buildInfo.getFile(mSystemImageZipName), + tmpDir); + if (buildInfo.getFile(mVbmetaImageZipName) != null) { + mVbmetaImg = + getRequestedFile( + device, + mVbmetaImageFileName, + buildInfo.getFile(mVbmetaImageZipName), + tmpDir); + } + if (buildInfo.getFile(mBootImageZipName) != null && mBootImageFileName != null) { mBootImg = getRequestedFile( - device, mBootImageFileName, buildInfo.getFile(GKI_BOOT_IMG), tmpDir); + device, + mBootImageFileName, + buildInfo.getFile(mBootImageZipName), + tmpDir); } } @@ -300,7 +316,10 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer { ZipUtil2.extractZip(sourceFile, destDir); requestedFile = FileUtil.findFile(destDir, requestedFileName); } catch (IOException e) { - throw new TargetSetupError(e.getMessage(), e, device.getDeviceDescriptor()); + throw new TargetSetupError( + String.format("Fail to get %s from %s", requestedFileName, sourceFile), + e, + device.getDeviceDescriptor()); } } else if (sourceFile.isDirectory()) { requestedFile = FileUtil.findFile(sourceFile, requestedFileName); @@ -343,7 +362,7 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer { throw new TargetSetupError( String.format( "fastboot command %s failed in device %s. stdout: %s, stderr: %s", - cmdArgs[0], + cmdArgs, device.getSerialNumber(), result.getStdout(), result.getStderr()), diff --git a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java index 3c8140778..936df0f63 100644 --- a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java +++ b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java @@ -65,15 +65,20 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller { private static final int R_SDK_INT = 30; private List<ApexInfo> mTestApexInfoList = new ArrayList<>(); + private List<ApexInfo> mModulesToUninstall = new ArrayList<>(); private Set<String> mApkToInstall = new LinkedHashSet<>(); private List<String> mApkInstalled = new ArrayList<>(); private List<String> mSplitsInstallArgs = new ArrayList<>(); private BundletoolUtil mBundletoolUtil; private String mDeviceSpecFilePath = ""; + private boolean mOptimizeMainlineTest = false; @Option(name = "bundletool-file-name", description = "The file name of the bundletool jar.") private String mBundletoolFilename; + @Option(name = "train-path", description = "The absoulte path of the train folder.") + private File mTrainFolderPath; + @Option( name = "apex-staging-wait-time", description = "The time in ms to wait for apex staged session ready.", @@ -88,18 +93,36 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller { + "preloaded on device. Otherwise an exception will be thrown.") private boolean mIgnoreIfNotPreloaded = false; + @Option( + name = "skip-apex-teardown", + description = "Skip teardown if all files to be installed are apex files. " + + "Currently, this option is only used for Test Mapping use case.") + private boolean mSkipApexTearDown = false; + @Override public void setUp(TestInformation testInfo) throws TargetSetupError, BuildError, DeviceNotAvailableException { setTestInformation(testInfo); ITestDevice device = testInfo.getDevice(); - if (getTestsFileName().isEmpty()) { + if (mTrainFolderPath != null) { + addApksToTestFiles(); + } + + List<File> moduleFileNames = getTestsFileName(); + if (moduleFileNames.isEmpty()) { CLog.i("No apk/apex module file to install. Skipping."); return; } - cleanUpStagedAndActiveSession(device); + if (!mSkipApexTearDown || hasApkFilesToInstall(moduleFileNames)) { + // Cleanup the device if skip-apex-teardown isn't set or not all files to be installed + // are apex files. It will always run with the target preparer. + cleanUpStagedAndActiveSession(device); + } + else { + mOptimizeMainlineTest = true; + } Set<ApexInfo> activatedApexes = device.getActiveApexes(); @@ -113,9 +136,36 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller { CLog.i("No modules are preloaded on the device, so no modules will be installed."); return; } + + if (mOptimizeMainlineTest) { + CLog.i("Optimizing install apex module target preparer."); + // Get the apex files that are already installed on the device. + Set<ApexInfo> apexInData = getApexInData(activatedApexes); + + // Get the apex files that are not used by the current test and will be uninstalled. + mModulesToUninstall.addAll( + getModulesToUninstall(apexInData, testAppFiles, device)); + + for (ApexInfo m : mModulesToUninstall) { + CLog.i("Uninstalling module: %s", m.name); + super.uninstallPackage(device, m.name); + } + + if (testAppFiles.isEmpty()) { + if (!mModulesToUninstall.isEmpty()) { + RunUtil.getDefault().sleep(mApexStagingWaitTime); + device.reboot(); + } + // If both the list of files to be installed and uninstalled are empty, that means + // the mainline modules are the same as the previous ones. + CLog.i("All required modules are installed"); + return; + } + } + if (containsApks(testAppFiles)) { installUsingBundleTool(testInfo, testAppFiles); - if (mTestApexInfoList.isEmpty()) { + if (mTestApexInfoList.isEmpty() && mModulesToUninstall.isEmpty()) { CLog.i("No Apex module in the train. Skipping reboot."); return; } else { @@ -164,8 +214,69 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller { CLog.i("Train activation succeed."); } + /** + * Get a set of modules that will be uninstalled. + * + * @param apexInData A Set<ApexInfo> of modules that are installed on the /data directory. + * @param testFiles A List<File> of modules that will be installed on the device. + * @param device the {@link ITestDevice} + * @return A Set<ApexInfo> of modules that will be uninstalled on the device. + */ + @VisibleForTesting + Set<ApexInfo> getModulesToUninstall(Set<ApexInfo> apexInData, + List<File> testFiles, ITestDevice device) throws TargetSetupError { + Set<ApexInfo> unInstallModules = new HashSet<>(apexInData); + List<File> filesToSkipInstall = new ArrayList<>(); + for (File testFile : testFiles) { + String packageName = parsePackageName(testFile, device.getDeviceDescriptor()); + for (ApexInfo apexModule : apexInData) { + if (apexModule.name.equals(packageName)) { + unInstallModules.remove(apexModule); + filesToSkipInstall.add(testFile); + } + } + } + // Update the modules to be installed based on what will not be installed. + testFiles.removeAll(filesToSkipInstall); + return unInstallModules; + } + + /** + * Return a set of files that is already installed on the /data directory. + */ + @VisibleForTesting + Set<ApexInfo> getApexInData(Set<ApexInfo> activatedApexes) { + Set<ApexInfo> apexInData = new HashSet<>(); + for (ApexInfo apex : activatedApexes) { + if (apex.sourceDir.startsWith(ACTIVATED_APEX_SOURCEDIR_PREFIX, 1)) { + apexInData.add(apex); + } + } + return apexInData; + } + + /** + * Check if the files to be installed contain .apk or .apks. + * + * @param testAppFiles List<File> of the modules that will be installed on the device. + * @return true if the files contain .apk or .apks, otherwise false. + */ + private boolean hasApkFilesToInstall(List<File> testAppFiles) { + List<String> checkLists = Arrays.asList(".apk", ".apks"); + for (File testAppFile : testAppFiles) { + if (checkLists.stream().anyMatch(entry -> testAppFile.getName().endsWith(entry))) { + return true; + } + } + return false; + } + @Override public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException { + if (mOptimizeMainlineTest) { + CLog.d("Skipping tearDown since the installed modules may be used for the next test."); + return; + } ITestDevice device = testInfo.getDevice(); if (e instanceof DeviceNotAvailableException) { CLog.e("Device %s is not available. Teardown() skipped.", device.getSerialNumber()); @@ -194,7 +305,13 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller { if (mBundletoolUtil != null) { return; } - File bundletoolJar = getLocalPathForFilename(testInfo, getBundletoolFileName()); + File bundletoolJar; + File f = new File(getBundletoolFileName()); + if (!f.isAbsolute()) { + bundletoolJar = getLocalPathForFilename(testInfo, getBundletoolFileName()); + } else { + bundletoolJar = f; + } if (bundletoolJar == null) { throw new TargetSetupError( String.format("Failed to find bundletool jar %s.", getBundletoolFileName()), @@ -474,7 +591,12 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller { throws TargetSetupError, DeviceNotAvailableException { ITestDevice device = testInfo.getDevice(); for (File moduleFileName : testAppFileNames) { - File moduleFile = getLocalPathForFilename(testInfo, moduleFileName.getName()); + File moduleFile; + if (!moduleFileName.isAbsolute()) { + moduleFile = getLocalPathForFilename(testInfo, moduleFileName.getName()); + } else { + moduleFile = moduleFileName; + } if (moduleFileName.getName().endsWith(SPLIT_APKS_SUFFIX)) { List<File> splits = getSplitsForApks(testInfo, moduleFile); String splitsArgs = createInstallArgsForSplit(splits, device); @@ -731,6 +853,16 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller { return failToActivateApex; } + private void addApksToTestFiles() { + File[] filesUnderTrainFolder = mTrainFolderPath.listFiles(); + Arrays.sort(filesUnderTrainFolder, (a, b) -> a.getName().compareTo(b.getName())); + for (File f : filesUnderTrainFolder) { + if (f.getName().endsWith(".apks")) { + getTestsFileName().add(f); + } + } + } + @VisibleForTesting protected String getBundletoolFileName() { return mBundletoolFilename; @@ -745,4 +877,9 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller { protected List<String> getApkInstalled() { return mApkInstalled; } + + @VisibleForTesting + public void setSkipApexTearDown(boolean skip) { + mSkipApexTearDown = skip; + } } diff --git a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java index 007c59af3..a33a532ed 100644 --- a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java +++ b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java @@ -33,6 +33,7 @@ import com.android.tradefed.result.error.InfraErrorIdentifier; import com.android.tradefed.testtype.IAbi; import com.android.tradefed.testtype.IAbiReceiver; import com.android.tradefed.util.AaptParser; +import com.android.tradefed.util.AaptParser.AaptVersion; import com.android.tradefed.util.AbiFormatter; import com.android.tradefed.util.BuildTestsZipUtils; @@ -129,6 +130,14 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei + "preparer does not verify if the apks are successfully removed.") private boolean mCleanup = true; + @VisibleForTesting static final String CHECK_MIN_SDK_OPTION = "check-min-sdk"; + + @Option( + name = CHECK_MIN_SDK_OPTION, + description = + "check app's min sdk prior to install and skip if device api level is too low.") + private boolean mCheckMinSdk = false; + /** @deprecated use test-file-name instead now that it is a File. */ @Deprecated @Option( @@ -152,6 +161,9 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei @Option(name = "instant-mode", description = "Whether or not to install apk in instant mode.") private boolean mInstantMode = false; + @Option(name = "aapt-version", description = "The version of AAPT for APK parsing.") + private AaptVersion mAaptVersion = AaptVersion.AAPT; + @Option( name = "force-install-mode", description = @@ -163,7 +175,7 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei private Integer mUserId = null; private Boolean mGrantPermission = null; - private Set<String> mPackagesInstalled = null; + private Set<String> mPackagesInstalled = new HashSet<>(); private TestInformation mTestInfo; protected void setTestInformation(TestInformation testInfo) { @@ -180,6 +192,12 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei addTestFile(new File(fileName)); } + /** Helper to parse an apk file with aapt. */ + @VisibleForTesting + AaptParser doAaptParse(File apkFile) { + return AaptParser.parse(apkFile); + } + @VisibleForTesting void clearTestFile() { mTestFiles.clear(); @@ -221,6 +239,11 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei mGrantPermission = shouldGrant; } + /** Sets the version of AAPT for APK parsing. */ + public void setAaptVersion(AaptVersion aaptVersion) { + mAaptVersion = aaptVersion; + } + /** Adds one apk installation arg to be used. */ public void addInstallArg(String arg) { mInstallArgs.add(arg); @@ -277,10 +300,6 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei CLog.i("No test apps to install, skipping"); return; } - if (mCleanup) { - mPackagesInstalled = new HashSet<>(); - } - // resolve abi flags if (mAbi != null && mForceAbi != null) { throw new IllegalStateException("cannot specify both abi flags: --abi and --force-abi"); @@ -291,7 +310,6 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei } else if (mForceAbi != null) { abiName = AbiFormatter.getDefaultAbi(getDevice(), mForceAbi); } - // Set all the extra install args outside the loop to avoid adding them several times. if (abiName != null && testInfo.getDevice().getApiLevel() > 20) { mInstallArgs.add(String.format("--abi %s", abiName)); @@ -370,7 +388,7 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei @Override public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException { mTestInfo = testInfo; - if (mCleanup && mPackagesInstalled != null && !(e instanceof DeviceNotAvailableException)) { + if (mCleanup && !(e instanceof DeviceNotAvailableException)) { for (String packageName : mPackagesInstalled) { try { uninstallPackage(getDevice(), packageName); @@ -454,8 +472,9 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei } /** Helper to resolve some apk to their File and Package. */ + @VisibleForTesting protected Map<File, String> resolveApkFiles(TestInformation testInfo, List<File> apkFiles) - throws TargetSetupError { + throws TargetSetupError, DeviceNotAvailableException { Map<File, String> appFiles = new LinkedHashMap<>(); ITestDevice device = testInfo.getDevice(); for (File apkFile : apkFiles) { @@ -488,7 +507,32 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei } } - appFiles.put(testAppFile, parsePackageName(testAppFile, device.getDeviceDescriptor())); + if (mCheckMinSdk) { + AaptParser aaptParser = doAaptParse(testAppFile); + if (aaptParser == null) { + throw new TargetSetupError( + String.format( + "Failed to extract info from `%s` using aapt", + testAppFile.getAbsoluteFile().getName()), + device.getDeviceDescriptor()); + } + if (device.getApiLevel() < aaptParser.getSdkVersion()) { + CLog.w( + "Skipping installing apk %s on device %s because " + + "SDK level require is %d, but device SDK level is %d", + apkFile.toString(), + device.getSerialNumber(), + aaptParser.getSdkVersion(), + device.getApiLevel()); + } else { + appFiles.put( + testAppFile, + parsePackageName(testAppFile, device.getDeviceDescriptor())); + } + } else { + appFiles.put( + testAppFile, parsePackageName(testAppFile, device.getDeviceDescriptor())); + } } return appFiles; } @@ -517,14 +561,16 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei String.format( "Could not list files of specified directory: %s", fileOrDirectory), e, - deviceDescriptor); + deviceDescriptor, + InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND); } if (mThrowIfNoFile && apkFiles.isEmpty()) { throw new TargetSetupError( String.format( "Could not find any files in specified directory: %s", fileOrDirectory), - deviceDescriptor); + deviceDescriptor, + InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND); } return apkFiles; @@ -588,7 +634,7 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei /** Get the package name from the test app. */ protected String parsePackageName(File testAppFile, DeviceDescriptor deviceDescriptor) throws TargetSetupError { - AaptParser parser = AaptParser.parse(testAppFile); + AaptParser parser = AaptParser.parse(testAppFile, mAaptVersion); if (parser == null) { throw new TargetSetupError( "apk installed but AaptParser failed", @@ -598,4 +644,3 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei return parser.getPackageName(); } } - diff --git a/src/com/android/tradefed/testtype/SubprocessTfLauncher.java b/src/com/android/tradefed/testtype/SubprocessTfLauncher.java index bbb8ea0e6..814ecf52a 100644 --- a/src/com/android/tradefed/testtype/SubprocessTfLauncher.java +++ b/src/com/android/tradefed/testtype/SubprocessTfLauncher.java @@ -23,6 +23,7 @@ import com.android.tradefed.config.IConfiguration; import com.android.tradefed.config.IConfigurationReceiver; import com.android.tradefed.config.Option; import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.error.HarnessRuntimeException; import com.android.tradefed.invoker.IInvocationContext; import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.log.LogUtil.CLog; @@ -31,6 +32,7 @@ import com.android.tradefed.result.FileInputStreamSource; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.LogDataType; import com.android.tradefed.result.TestDescription; +import com.android.tradefed.result.error.InfraErrorIdentifier; import com.android.tradefed.result.proto.StreamProtoReceiver; import com.android.tradefed.result.proto.StreamProtoResultReporter; import com.android.tradefed.util.CommandResult; @@ -409,11 +411,12 @@ public abstract class SubprocessTfLauncher if (result.getStatus().equals(CommandStatus.TIMED_OUT)) { errMessage = String.format("Timeout after %s", TimeUtil.formatElapsedTime(mMaxTfRunTime)); - throw new RuntimeException( + throw new HarnessRuntimeException( String.format( "%s Tests subprocess failed due to:\n%s\n", - mConfigName, errMessage)); - } else { + mConfigName, errMessage), + InfraErrorIdentifier.INVOCATION_TIMEOUT); + } else if (eventParser != null && !eventParser.reportedInvocationFailed()) { SubprocessExceptionParser.handleStderrException(result); } } diff --git a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java index 5c6348d6a..9ded1886a 100644 --- a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java +++ b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java @@ -284,6 +284,8 @@ public class BaseTestSuite extends ITestSuite { if (mEnableMainlineParameter) { mModuleRepo.setMainlineParameterizedModules(mEnableMainlineParameter); mModuleRepo.setInvocationContext(getInvocationContext()); + mModuleRepo.setOptimizeMainlineTest( + getConfiguration().getCommandOptions().getOptimizeMainlineTest()); } mModuleRepo.setParameterizedModules(mEnableParameter); diff --git a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java index a9434ed39..5400ddd7c 100644 --- a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java +++ b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java @@ -73,6 +73,7 @@ public class GranularRetriableTestWrapper implements IRemoteTest, ITestCollector private IRetryDecision mRetryDecision; private IRemoteTest mTest; + private ModuleDefinition mModule; private List<IMetricCollector> mRunMetricCollectors; private TestFailureListener mFailureListener; private IInvocationContext mModuleInvocationContext; @@ -95,7 +96,18 @@ public class GranularRetriableTestWrapper implements IRemoteTest, ITestCollector TestFailureListener failureListener, List<ITestInvocationListener> moduleLevelListeners, int maxRunLimit) { + this(test, null, mainListener, failureListener, moduleLevelListeners, maxRunLimit); + } + + public GranularRetriableTestWrapper( + IRemoteTest test, + ModuleDefinition module, + ITestInvocationListener mainListener, + TestFailureListener failureListener, + List<ITestInvocationListener> moduleLevelListeners, + int maxRunLimit) { mTest = test; + mModule = module; mMainGranularRunListener = new ModuleListener(mainListener); mFailureListener = failureListener; mModuleLevelListeners = moduleLevelListeners; @@ -230,7 +242,7 @@ public class GranularRetriableTestWrapper implements IRemoteTest, ITestCollector // Bail out early if there is no need to retry at all. if (!mRetryDecision.shouldRetry( - mTest, 0, mMainGranularRunListener.getTestRunForAttempts(0))) { + mTest, mModule, 0, mMainGranularRunListener.getTestRunForAttempts(0))) { return; } // Avoid rechecking the shouldRetry below the first time as it could retrigger reboot. @@ -245,6 +257,7 @@ public class GranularRetriableTestWrapper implements IRemoteTest, ITestCollector boolean retry = mRetryDecision.shouldRetry( mTest, + mModule, attemptNumber - 1, mMainGranularRunListener.getTestRunForAttempts( attemptNumber - 1)); diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java index 3561868d5..3183e9c13 100644 --- a/src/com/android/tradefed/testtype/suite/ITestSuite.java +++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java @@ -69,13 +69,17 @@ import com.android.tradefed.testtype.ITestCollector; import com.android.tradefed.util.AbiFormatter; import com.android.tradefed.util.AbiUtils; import com.android.tradefed.util.MultiMap; +import com.android.tradefed.util.StreamUtil; import com.android.tradefed.util.TimeUtil; +import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import com.google.inject.Injector; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; @@ -131,6 +135,9 @@ public abstract class ITestSuite private static final String PRODUCT_CPU_ABI_KEY = "ro.product.cpu.abi"; + private static final Set<String> ALLOWED_PREPARERS_CONFIGS = + ImmutableSet.of("/suite/allowed-preparers.txt", "/suite/google-allowed-preparers.txt"); + // Options for test failure case @Option( name = "bugreport-on-failure", @@ -509,6 +516,9 @@ public abstract class ITestSuite return runModules; } + Map<String, List<ITargetPreparer>> suitePreparersPerDevice = + getAllowedPreparerPerDevice(mMainConfiguration); + for (Entry<String, IConfiguration> config : runConfig.entrySet()) { // Validate the configuration, it will throw if not valid. ValidateSuiteConfigHelper.validateConfig(config.getValue()); @@ -519,6 +529,7 @@ public abstract class ITestSuite config.getKey(), config.getValue().getTests(), preparersPerDevice, + suitePreparersPerDevice, config.getValue().getMultiTargetPreparers(), config.getValue()); if (mDisableAutoRetryTimeReporting) { @@ -585,6 +596,40 @@ public abstract class ITestSuite return res; } + /** Create the mapping of device to its target_preparer that's allowed to rerun. */ + private Map<String, List<ITargetPreparer>> getAllowedPreparerPerDevice(IConfiguration config) { + // For unittests, mMainConfiguration might not have been set. + if (config == null) { + return new LinkedHashMap<String, List<ITargetPreparer>>(); + } + // Read the list of allowed suite level target preparers from resource files. + Set<String> allowedSuitePreparers = new HashSet<>(); + for (String resource : ALLOWED_PREPARERS_CONFIGS) { + try (InputStream resStream = ITestSuite.class.getResourceAsStream(resource)) { + if (resStream == null) { + CLog.d("Resource not found for allowed preparers: %s", resource); + continue; + } + List<String> preparers = + Arrays.asList(StreamUtil.getStringFromStream(resStream).split("\n")); + allowedSuitePreparers.addAll(preparers); + } catch (IOException e) { + CLog.e(e); + } + } + + Map<String, List<ITargetPreparer>> res = new LinkedHashMap<>(); + for (IDeviceConfiguration holder : config.getDeviceConfig()) { + List<ITargetPreparer> preparers = new ArrayList<>(); + for (ITargetPreparer preparer : holder.getTargetPreparers()) { + if (allowedSuitePreparers.contains(preparer.getClass().getCanonicalName())) + preparers.add(preparer); + } + res.put(holder.getDeviceName(), preparers); + } + return res; + } + /** * Opportunity to clean up all the things that were needed during the suites setup but are not * required to run the tests. @@ -948,6 +993,7 @@ public abstract class ITestSuite ModuleSplitter.splitConfiguration( testInfo, runConfig, + getAllowedPreparerPerDevice(mMainConfiguration), shardCountHint, mShouldMakeDynamicModule, mIntraModuleSharding); diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java index 0d29944e3..2157877b2 100644 --- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java +++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java @@ -59,6 +59,7 @@ import com.android.tradefed.result.TestResult; import com.android.tradefed.result.TestRunResult; import com.android.tradefed.result.error.ErrorIdentifier; import com.android.tradefed.result.error.InfraErrorIdentifier; +import com.android.tradefed.result.error.TestErrorIdentifier; import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; import com.android.tradefed.retry.IRetryDecision; import com.android.tradefed.retry.RetryStatistics; @@ -125,10 +126,13 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl private IConfiguration mInternalTestConfiguration; private IConfiguration mInternalTargetPreparerConfiguration; private ILogSaver mLogSaver; + private TestInformation mModuleInfo; + private ITestInvocationListener mInvocationListener; private final String mId; private Collection<IRemoteTest> mTests = null; private Map<String, List<ITargetPreparer>> mPreparersPerDevice = null; + private Map<String, List<ITargetPreparer>> mSuitePreparersPerDevice = null; private List<IMultiTargetPreparer> mMultiPreparers = new ArrayList<>(); private IBuildInfo mBuild; @@ -174,6 +178,24 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl Map<String, List<ITargetPreparer>> preparersPerDevice, List<IMultiTargetPreparer> multiPreparers, IConfiguration moduleConfig) { + this(name, tests, preparersPerDevice, null, multiPreparers, moduleConfig); + } + + /** + * Constructor + * + * @param name unique name of the test configuration. + * @param tests list of {@link IRemoteTest} that needs to run. + * @param preparersPerDevice list of {@link ITargetPreparer} to be used to setup the device. + * @param moduleConfig the {@link IConfiguration} of the underlying module config. + */ + public ModuleDefinition( + String name, + Collection<IRemoteTest> tests, + Map<String, List<ITargetPreparer>> preparersPerDevice, + Map<String, List<ITargetPreparer>> suitePreparersPerDevice, + List<IMultiTargetPreparer> multiPreparers, + IConfiguration moduleConfig) { mId = name; mTests = tests; mModuleConfiguration = moduleConfig; @@ -204,6 +226,7 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl mMultiPreparers.addAll(multiPreparers); mPreparersPerDevice = preparersPerDevice; + mSuitePreparersPerDevice = suitePreparersPerDevice; // Get the tokens of the module List<String> tokens = configDescriptor.getMetaData(ITestSuite.TOKEN_KEY); @@ -343,6 +366,9 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl TestFailureListener failureListener, int maxRunLimit) throws DeviceNotAvailableException { + mModuleInfo = moduleInfo; + mInvocationListener = listener; + mStartModuleRunDate = System.currentTimeMillis(); // Load extra configuration for the module from module_controller // TODO: make module_controller a full TF object @@ -390,22 +416,10 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl moduleInfo.getDevice(), mInternalTargetPreparerConfiguration); } // Setup - long prepStartTime = getCurrentTime(); if (preparationException == null) { - preparationException = runTargetPreparation(moduleInfo, listener); + preparationException = runPreparation(false); } - // Skip multi-preparation if preparation already failed. - if (preparationException == null) { - for (IMultiTargetPreparer multiPreparer : mMultiPreparers) { - preparationException = runMultiPreparerSetup(multiPreparer, moduleInfo, listener); - if (preparationException != null) { - mIsFailedModule = true; - CLog.e("Some preparation step failed. failing the module %s", getId()); - break; - } - } - } - mElapsedPreparation = getCurrentTime() - prepStartTime; + // Run the tests try { if (preparationException != null) { @@ -604,7 +618,7 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl int maxRunLimit) { GranularRetriableTestWrapper retriableTest = new GranularRetriableTestWrapper( - test, listener, failureListener, moduleLevelListeners, maxRunLimit); + test, this, listener, failureListener, moduleLevelListeners, maxRunLimit); retriableTest.setModuleId(getId()); retriableTest.setMarkTestsSkipped(skipTestCases); retriableTest.setMetricCollectors(mRunMetricCollectors); @@ -781,11 +795,41 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl } } + /** + * Run preparers of the test, including suite level preparers if specified. + * + * @param includeSuitePreparers Set to {@code true} to also run suite level preparers. + * @return {@link Throwable} of any exception raised when running preparers. + */ + public Throwable runPreparation(boolean includeSuitePreparers) { + Throwable preparationException = null; + long prepStartTime = getCurrentTime(); + if (includeSuitePreparers) { + // Run suite level preparers. + preparationException = runTargetPreparation(mSuitePreparersPerDevice); + } + + if (preparationException == null) { + preparationException = runTargetPreparation(mPreparersPerDevice); + } + // Skip multi-preparation if preparation already failed. + if (preparationException == null) { + for (IMultiTargetPreparer multiPreparer : mMultiPreparers) { + preparationException = runMultiPreparerSetup(multiPreparer); + if (preparationException != null) { + mIsFailedModule = true; + CLog.e("Some preparation step failed. failing the module %s", getId()); + break; + } + } + } + mElapsedPreparation = getCurrentTime() - prepStartTime; + return preparationException; + } + /** Run all the prepare steps. */ private Throwable runPreparerSetup( - TestInformation moduleInfo, ITargetPreparer preparer, - ITestLogger logger, int deviceIndex) { if (preparer.isDisabled()) { // If disabled skip completely. @@ -796,14 +840,14 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl try { // set the logger in case they need it. if (preparer instanceof ITestLoggerReceiver) { - ((ITestLoggerReceiver) preparer).setTestLogger(logger); + ((ITestLoggerReceiver) preparer).setTestLogger(mInvocationListener); } if (preparer instanceof IInvocationContextReceiver) { ((IInvocationContextReceiver) preparer) .setInvocationContext(mModuleInvocationContext); } - moduleInfo.setActiveDeviceIndex(deviceIndex); - preparer.setUp(moduleInfo); + mModuleInfo.setActiveDeviceIndex(deviceIndex); + preparer.setUp(mModuleInfo); return null; } catch (BuildError | TargetSetupError @@ -817,13 +861,12 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl CLog.e(e); return e; } finally { - moduleInfo.setActiveDeviceIndex(0); + mModuleInfo.setActiveDeviceIndex(0); } } /** Run all multi target preparer step. */ - private Throwable runMultiPreparerSetup( - IMultiTargetPreparer preparer, TestInformation moduleInfo, ITestLogger logger) { + private Throwable runMultiPreparerSetup(IMultiTargetPreparer preparer) { if (preparer.isDisabled()) { // If disabled skip completely. return null; @@ -833,13 +876,13 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl try { // set the logger in case they need it. if (preparer instanceof ITestLoggerReceiver) { - ((ITestLoggerReceiver) preparer).setTestLogger(logger); + ((ITestLoggerReceiver) preparer).setTestLogger(mInvocationListener); } if (preparer instanceof IInvocationContextReceiver) { ((IInvocationContextReceiver) preparer) .setInvocationContext(mModuleInvocationContext); } - preparer.setUp(moduleInfo); + preparer.setUp(mModuleInfo); return null; } catch (BuildError | TargetSetupError @@ -991,6 +1034,14 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl } /** + * Returns the list of suite level {@link ITargetPreparer} associated with the given device name + */ + @VisibleForTesting + List<ITargetPreparer> getSuitePreparerForDevice(String deviceName) { + return mSuitePreparersPerDevice.get(deviceName); + } + + /** * When running unit tests for ModuleDefinition we don't want to unnecessarily report some auto * retry times. */ @@ -1011,7 +1062,9 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl } listener.testRunStarted(getId(), 0, 0, System.currentTimeMillis()); FailureDescription description = - FailureDescription.create(message).setFailureStatus(FailureStatus.NOT_EXECUTED); + FailureDescription.create(message) + .setFailureStatus(FailureStatus.NOT_EXECUTED) + .setErrorIdentifier(TestErrorIdentifier.MODULE_DID_NOT_EXECUTE); listener.testRunFailed(description); listener.testRunEnded(0, new HashMap<String, Metric>()); listener.testModuleEnded(); @@ -1059,28 +1112,28 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl InvocationMetricKey.AUTO_RETRY_TIME, retryTimeMs); } - private Throwable runTargetPreparation(TestInformation moduleInfo, ITestLogger logger) { + private Throwable runTargetPreparation(Map<String, List<ITargetPreparer>> preparersPerDevice) { Throwable preparationException = null; for (int i = 0; i < mModuleInvocationContext.getDeviceConfigNames().size(); i++) { String deviceName = mModuleInvocationContext.getDeviceConfigNames().get(i); - if (i >= mPreparersPerDevice.size()) { + if (i >= preparersPerDevice.size()) { CLog.d( "Main configuration has more devices than the module configuration. '%s' " + "will not run any preparation.", deviceName); continue; } - List<ITargetPreparer> preparers = mPreparersPerDevice.get(deviceName); + List<ITargetPreparer> preparers = preparersPerDevice.get(deviceName); if (preparers == null) { CLog.w( "Module configuration devices mismatch the main configuration " + "(Missing device '%s'), resolving preparers by index.", deviceName); - String key = new ArrayList<>(mPreparersPerDevice.keySet()).get(i); - preparers = mPreparersPerDevice.get(key); + String key = new ArrayList<>(preparersPerDevice.keySet()).get(i); + preparers = preparersPerDevice.get(key); } for (ITargetPreparer preparer : preparers) { - preparationException = runPreparerSetup(moduleInfo, preparer, logger, i); + preparationException = runPreparerSetup(preparer, i); if (preparationException != null) { mIsFailedModule = true; CLog.e("Some preparation step failed. failing the module %s", getId()); diff --git a/src/com/android/tradefed/testtype/suite/ModuleSplitter.java b/src/com/android/tradefed/testtype/suite/ModuleSplitter.java index a4fa85f88..970522aea 100644 --- a/src/com/android/tradefed/testtype/suite/ModuleSplitter.java +++ b/src/com/android/tradefed/testtype/suite/ModuleSplitter.java @@ -63,6 +63,7 @@ public class ModuleSplitter { * * @param testInfo the current {@link TestInformation} to proceed with sharding. * @param runConfig {@link LinkedHashMap} loaded from {@link ITestSuite#loadTests()}. + * @param suitePreparersPerDevice map of suite level preparers per test device. * @param shardCount a shard count hint to help with sharding. * @param dynamicModule Whether or not module can be shared in pool or must be independent * (strict sharding). @@ -72,6 +73,7 @@ public class ModuleSplitter { public static List<ModuleDefinition> splitConfiguration( TestInformation testInfo, LinkedHashMap<String, IConfiguration> runConfig, + Map<String, List<ITargetPreparer>> suitePreparersPerDevice, int shardCount, boolean dynamicModule, boolean intraModuleSharding) { @@ -93,7 +95,8 @@ public class ModuleSplitter { configMap.getValue(), shardCount, dynamicModule, - intraModuleSharding); + intraModuleSharding, + suitePreparersPerDevice); } catch (RuntimeException e) { CLog.e("Exception while creating module for '%s'", configMap.getKey()); throw e; @@ -109,7 +112,8 @@ public class ModuleSplitter { IConfiguration config, int shardCount, boolean dynamicModule, - boolean intraModuleSharding) { + boolean intraModuleSharding, + Map<String, List<ITargetPreparer>> suitePreparersPerDevice) { List<IRemoteTest> tests = config.getTests(); // Get rid of the IRemoteTest reference on the shared configuration. It will not be used // to run. @@ -127,11 +131,13 @@ public class ModuleSplitter { moduleName, tests, clonePreparersMap(config), + clonePreparersMap(suitePreparersPerDevice), clonePreparers(config.getMultiTargetPreparers()), config); currentList.add(module); } else { - addModuleToListFromSingleTest(currentList, tests.get(i), moduleName, config); + addModuleToListFromSingleTest( + currentList, tests.get(i), moduleName, config, suitePreparersPerDevice); } } clearPreparersFromConfig(config); @@ -153,6 +159,7 @@ public class ModuleSplitter { moduleName, shardedTests, clonePreparersMap(config), + clonePreparersMap(suitePreparersPerDevice), clonePreparers(config.getMultiTargetPreparers()), config); currentList.add(module); @@ -161,14 +168,19 @@ public class ModuleSplitter { // We create independent modules with each sharded test. for (IRemoteTest moduleTest : shardedTests) { addModuleToListFromSingleTest( - currentList, moduleTest, moduleName, config); + currentList, + moduleTest, + moduleName, + config, + suitePreparersPerDevice); } } continue; } } // test is not shardable or did not shard - addModuleToListFromSingleTest(currentList, test, moduleName, config); + addModuleToListFromSingleTest( + currentList, test, moduleName, config, suitePreparersPerDevice); } clearPreparersFromConfig(config); } @@ -181,7 +193,8 @@ public class ModuleSplitter { List<ModuleDefinition> currentList, IRemoteTest test, String moduleName, - IConfiguration config) { + IConfiguration config, + Map<String, List<ITargetPreparer>> suitePreparersPerDevice) { List<IRemoteTest> testList = new ArrayList<>(); testList.add(test); ModuleDefinition module = @@ -189,6 +202,7 @@ public class ModuleSplitter { moduleName, testList, clonePreparersMap(config), + clonePreparersMap(suitePreparersPerDevice), clonePreparers(config.getMultiTargetPreparers()), config); currentList.add(module); @@ -234,6 +248,18 @@ public class ModuleSplitter { return res; } + /** Deep cloning of potentially multi-device preparers. */ + private static Map<String, List<ITargetPreparer>> clonePreparersMap( + Map<String, List<ITargetPreparer>> suitePreparersPerDevice) { + Map<String, List<ITargetPreparer>> res = new LinkedHashMap<>(); + for (String device : suitePreparersPerDevice.keySet()) { + List<ITargetPreparer> preparers = new ArrayList<>(); + res.put(device, preparers); + preparers.addAll(clonePreparers(suitePreparersPerDevice.get(device))); + } + return res; + } + private static void clearPreparersFromConfig(IConfiguration config) { try { for (IDeviceConfiguration holder : config.getDeviceConfig()) { diff --git a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java index 26a7f4ab2..2831e8bba 100644 --- a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java +++ b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java @@ -79,6 +79,7 @@ public class SuiteModuleLoader { private boolean mAllowParameterizedModules = false; private boolean mAllowMainlineParameterizedModules = false; + private boolean mOptimizeMainlineTest = false; private boolean mAllowOptionalParameterizedModules = false; private ModuleParameters mForcedModuleParameter = null; private Set<ModuleParameters> mExcludedModuleParameters = new HashSet<>(); @@ -121,6 +122,11 @@ public class SuiteModuleLoader { mAllowMainlineParameterizedModules = allowed; } + /** Sets whether or not to optimize mainline test. */ + public final void setOptimizeMainlineTest(boolean allowed) { + mOptimizeMainlineTest = allowed; + } + /** Sets whether or not to allow optional parameterized modules. */ public final void setOptionalParameterizedModules(boolean allowed) { mAllowOptionalParameterizedModules = allowed; @@ -378,7 +384,8 @@ public class SuiteModuleLoader { new MainlineModuleHandler( param, abi, - mContext + mContext, + mOptimizeMainlineTest ); skipCreatingBaseConfig = true; IConfiguration paramConfig = diff --git a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java index da1741fa0..60825170b 100644 --- a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java +++ b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java @@ -219,9 +219,9 @@ public class TestMappingSuiteRunner extends BaseTestSuite { if (configPath == null) { throw new RuntimeException(String.format("Configuration path is null.")); } - File configFie = new File(configPath); - if (!configFie.exists()) { - configFie = null; + File configFile = new File(configPath); + if (!configFile.exists()) { + configFile = null; } // De-duplicate test infos so that there won't be duplicate test options. testInfos = dedupTestInfos(testInfos); @@ -229,10 +229,10 @@ public class TestMappingSuiteRunner extends BaseTestSuite { // Clean up all the test options injected in SuiteModuleLoader. super.cleanUpSuiteSetup(); super.clearModuleArgs(); - if (configFie != null) { + if (configFile != null) { clearConfigPaths(); // Set config path to BaseTestSuite to limit the search. - addConfigPaths(configFie); + addConfigPaths(configFile); } // Inject the test options from each test info to SuiteModuleLoader. parseOptions(testInfo); diff --git a/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java b/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java index eb4f4a4f0..9f4af97ca 100644 --- a/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java +++ b/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java @@ -38,11 +38,17 @@ public final class MainlineModuleHandler { private String mDynamicBaseLink = null; private IAbi mAbi = null; private String mName = null; + private boolean mOptimizeMainlineTest = false; - public MainlineModuleHandler(String name, IAbi abi, IInvocationContext context) { + public MainlineModuleHandler( + String name, + IAbi abi, + IInvocationContext context, + boolean optimize) { mName = name; mAbi = abi; buildDynamicBaseLink(context.getBuildInfos().get(0)); + mOptimizeMainlineTest = optimize; } /** Builds the dynamic base link where the mainline modules would be downloaded. */ @@ -78,6 +84,7 @@ public final class MainlineModuleHandler { private InstallApexModuleTargetPreparer createMainlineModuleInstaller() { InstallApexModuleTargetPreparer mainlineModuleInstaller = new InstallApexModuleTargetPreparer(); + mainlineModuleInstaller.setSkipApexTearDown(mOptimizeMainlineTest); // Inject the real dynamic link to the target preparer so that it will dynamically download // the mainline modules. String fullDynamicLink = mDynamicBaseLink; diff --git a/src/com/android/tradefed/util/AaptParser.java b/src/com/android/tradefed/util/AaptParser.java index a88fb8f5b..a61d230ef 100644 --- a/src/com/android/tradefed/util/AaptParser.java +++ b/src/com/android/tradefed/util/AaptParser.java @@ -54,6 +54,48 @@ public class AaptParser { private static final int AAPT_TIMEOUT_MS = 60000; private static final int INVALID_SDK = -1; + /** + * Enum of options for AAPT version used to parse APK files. + */ + public static enum AaptVersion { + AAPT { + @Override + public String[] dumpBadgingCommand(File apkFile) { + return new String[] {"aapt", "dump", "badging", apkFile.getAbsolutePath()}; + } + + @Override + public String[] dumpXmlTreeCommand(File apkFile) { + return new String[] { + "aapt", "dump", "xmltree", apkFile.getAbsolutePath(), "AndroidManifest.xml" + }; + } + }, + + AAPT2 { + @Override + public String[] dumpBadgingCommand(File apkFile) { + return new String[] {"aapt2", "dump", "badging", apkFile.getAbsolutePath()}; + } + + @Override + public String[] dumpXmlTreeCommand(File apkFile) { + return new String[] { + "aapt2", + "dump", + "xmltree", + apkFile.getAbsolutePath(), + "--file", + "AndroidManifest.xml" + }; + } + }; + + public abstract String[] dumpBadgingCommand(File apkFile); + + public abstract String[] dumpXmlTreeCommand(File apkFile); + }; + private String mPackageName; private String mVersionCode; private String mVersionName; @@ -130,16 +172,21 @@ public class AaptParser { * @return the {@link AaptParser} or <code>null</code> if failed to extract the information */ public static AaptParser parse(File apkFile) { + return parse(apkFile, AaptVersion.AAPT); + } + + /** + * Parse info from the apk. + * + * @param apkFile the apk file + * @param aaptVersion the aapt version + * @return the {@link AaptParser} or <code>null</code> if failed to extract the information + */ + public static AaptParser parse(File apkFile, AaptVersion aaptVersion) { CommandResult result = RunUtil.getDefault() .runTimedCmdRetry( - AAPT_TIMEOUT_MS, - 0L, - 2, - "aapt", - "dump", - "badging", - apkFile.getAbsolutePath()); + AAPT_TIMEOUT_MS, 0L, 2, aaptVersion.dumpBadgingCommand(apkFile)); String stderr = result.getStderr(); if (stderr != null && !stderr.isEmpty()) { @@ -158,11 +205,7 @@ public class AaptParser { AAPT_TIMEOUT_MS, 0L, 2, - "aapt", - "dump", - "xmltree", - apkFile.getAbsolutePath(), - "AndroidManifest.xml"); + aaptVersion.dumpXmlTreeCommand(apkFile)); stderr = result.getStderr(); if (stderr != null && !stderr.isEmpty()) { diff --git a/src/com/android/tradefed/util/ProtoUtil.java b/src/com/android/tradefed/util/ProtoUtil.java new file mode 100644 index 000000000..bf1699abf --- /dev/null +++ b/src/com/android/tradefed/util/ProtoUtil.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.util; + +import com.android.tradefed.log.LogUtil.CLog; + +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Message; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** Utility methods for dealing with protobuf messages type-agnostically. */ +public class ProtoUtil { + + /** + * Get values of a nested field reference, i.e. field_1.field_2.field_3, from a proto message as + * a list of strings. Returns an empty list when a field cannot be found. + * + * <p>If the field reference contains repeated fields, each instance is expanded, resulting in a + * list of strings. + * + * @param message The protobuf {@link Message} or object to be parsed. + * @param references A list of field references starting at the root of the message. e.g. if we + * want to read {@code field_2} under the value of {@code field_1} in {@code + * messageOrObject} the list would be {@code field1}, {@code field2}. + * @return A list of all the fields values referred to by the reference. If {@code references} + * is empty, returns {@code message.toString()} as a list. If {@code references} is invalid, + * returns an empty list. + */ + public static List<String> getNestedFieldFromMessageAsStrings( + Message message, List<String> references) { + return getNestedFieldFromMessageAsStringsHelper(message, references); + } + + /** + * A helper method to {@code getNestedFieldFromMessageAsStrings} where the "message" can be an + * object in case we reach a primitive value field during recursive parsing. + */ + private static List<String> getNestedFieldFromMessageAsStringsHelper( + Object messageOrObject, List<String> references) { + if (references.isEmpty()) { + return Arrays.asList(String.valueOf(messageOrObject)); + } + if (!(messageOrObject instanceof Message)) { + CLog.e( + "Attempting to read field %s from object of type %s, " + + "which is not a proto message.", + references.get(0), messageOrObject.getClass()); + return new ArrayList<String>(); + } + Message message = (Message) messageOrObject; + String reference = references.get(0); + FieldDescriptor fieldDescriptor = message.getDescriptorForType().findFieldByName(reference); + if (fieldDescriptor == null) { + CLog.e("Could not find field %s in message %s.", reference, message); + return new ArrayList<String>(); + } + Object fieldValue = message.getField(fieldDescriptor); + if (fieldValue instanceof List) { + return ((List<? extends Object>) fieldValue) + .stream() + .flatMap( + v -> + getNestedFieldFromMessageAsStringsHelper( + v, references.subList(1, references.size())) + .stream()) + .collect(Collectors.toList()); + } + return getNestedFieldFromMessageAsStringsHelper( + fieldValue, references.subList(1, references.size())); + } +} diff --git a/src/com/android/tradefed/util/StringEscapeUtils.java b/src/com/android/tradefed/util/StringEscapeUtils.java index 846c0e7de..4cc574114 100644 --- a/src/com/android/tradefed/util/StringEscapeUtils.java +++ b/src/com/android/tradefed/util/StringEscapeUtils.java @@ -47,6 +47,15 @@ public class StringEscapeUtils { case '\\': out.append("\\\\"); break; + case '>': + out.append("\\>"); + break; + case '<': + out.append("\\<"); + break; + case '|': + out.append("\\|"); + break; default: out.append(ch); break; diff --git a/src/com/android/tradefed/util/SubprocessTestResultsParser.java b/src/com/android/tradefed/util/SubprocessTestResultsParser.java index 2a5d280ed..c560e6ab5 100644 --- a/src/com/android/tradefed/util/SubprocessTestResultsParser.java +++ b/src/com/android/tradefed/util/SubprocessTestResultsParser.java @@ -80,6 +80,7 @@ public class SubprocessTestResultsParser implements Closeable { private TestDescription mCurrentTest = null; private IInvocationContext mCurrentModuleContext = null; + private InvocationFailedEventInfo mReportedInvocationFailedEventInfo = null; private Pattern mPattern = null; private Map<String, EventHandler> mHandlerMap = null; @@ -423,6 +424,7 @@ public class SubprocessTestResultsParser implements Closeable { } else { mListener.invocationFailed(ifi.mCause); } + mReportedInvocationFailedEventInfo = ifi; } } @@ -653,4 +655,14 @@ public class SubprocessTestResultsParser implements Closeable { public TestDescription getCurrentTest() { return mCurrentTest; } + + /** Returns whether or not an invocation failed was reported. */ + public boolean reportedInvocationFailed() { + return (mReportedInvocationFailedEventInfo != null); + } + + /** Returns reported invocation failure event info. */ + public InvocationFailedEventInfo getReportedInvocationFailedEventInfo() { + return mReportedInvocationFailedEventInfo; + } } diff --git a/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java b/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java index ee6cc2380..7a94bf5c4 100644 --- a/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java +++ b/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java @@ -20,13 +20,13 @@ import com.android.tradefed.log.LogUtil.CLog; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; /** Wrapper of {@link ExecutorService} to execute a function in parallel. */ public class ParallelDeviceExecutor<V> { @@ -43,7 +43,7 @@ public class ParallelDeviceExecutor<V> { * Invoke all the {@link Callable} with the timeout limit. * * @param callableTasks The List of tasks. - * @param timeout The timeout to apply. + * @param timeout The timeout to apply, or zero for unlimited. * @param unit The unit of the timeout. * @return The list of results for each callable task. */ @@ -61,12 +61,15 @@ public class ParallelDeviceExecutor<V> { }); List<V> results = new ArrayList<>(); try { - List<Future<V>> futures = executor.invokeAll(callableTasks); + List<Future<V>> futures = + timeout == 0L + ? executor.invokeAll(callableTasks) + : executor.invokeAll(callableTasks, timeout, unit); for (Future<V> future : futures) { try { - results.add(future.get(timeout, unit)); - } catch (TimeoutException timeoutException) { - mErrors.add(timeoutException); + results.add(future.get()); + } catch (CancellationException cancellationException) { + mErrors.add(cancellationException); } catch (ExecutionException execException) { mErrors.add(execException.getCause()); } diff --git a/test_framework/Android.bp b/test_framework/Android.bp index 31d31a78f..75e34cc10 100644 --- a/test_framework/Android.bp +++ b/test_framework/Android.bp @@ -19,6 +19,7 @@ java_library_host { "com/**/*.java", ], static_libs: [ + "diffutils-prebuilt-jar", "longevity-host-lib", "perfetto_metrics-full", "test-composers", diff --git a/test_framework/com/android/tradefed/device/metric/AtraceCollector.java b/test_framework/com/android/tradefed/device/metric/AtraceCollector.java index c6ea63b1a..5a3203527 100644 --- a/test_framework/com/android/tradefed/device/metric/AtraceCollector.java +++ b/test_framework/com/android/tradefed/device/metric/AtraceCollector.java @@ -27,6 +27,7 @@ import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; import com.android.tradefed.result.FileInputStreamSource; import com.android.tradefed.result.LogDataType; +import com.android.tradefed.result.error.DeviceErrorIdentifier; import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.FileUtil; import com.android.tradefed.util.IRunUtil; @@ -268,7 +269,9 @@ public class AtraceCollector extends BaseDeviceMetricCollector { postProcess(trace); trace.delete(); } else { - throw new DeviceRuntimeException("failed to pull log: " + fullLogPath()); + throw new DeviceRuntimeException( + String.format("failed to pull log: %s", fullLogPath()), + DeviceErrorIdentifier.FAIL_PULL_FILE); } if (!mPreserveOndeviceLog) { diff --git a/test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java deleted file mode 100644 index 304985e4b..000000000 --- a/test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.result.FileInputStreamSource; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; -import com.google.common.io.Files; -import java.io.File; -import java.io.IOException; - -/** A {@link ScheduledDeviceMetricCollector} to collect fragmentation at regular intervals. */ -public class BuddyInfoMetricCollector extends ScheduledDeviceMetricCollector { - public BuddyInfoMetricCollector() { - setTag("fragmentation"); - } - - @Override - void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { - try { - CLog.i("Running unusable-index collector..."); - String outputFileName = - String.format("%s/unusable-index-%s", createTempDir(), getFileSuffix()); - File outputFile = - saveProcessOutput(device, "cat /d/extfrag/unusable_index", outputFileName); - try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) { - getInvocationListener() - .testLog( - Files.getNameWithoutExtension(outputFile.getName()), - LogDataType.TEXT, - source); - } - - } catch (DeviceNotAvailableException | IOException e) { - CLog.e(e); - } - } -} diff --git a/test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java b/test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java deleted file mode 100644 index 8bf0dd6a7..000000000 --- a/test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tradefed.device.metric; - -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; - -/** A {@link ScheduledDeviceMetricCollector} to collect zipped bugreport at regular intervals. */ -public class BugreportzMetricCollector extends ScheduledDeviceMetricCollector { - public BugreportzMetricCollector() { - setTag("bugreportz"); - } - - @Override - void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { - CLog.i("Running bugreportz..."); - - String hostBugreportFilename = String.format("bugreport-%s", getFileSuffix()); - if (!device.logBugreport(hostBugreportFilename, getInvocationListener())) { - CLog.e("Failed to run bugreportz or bugreport."); - } - } -} diff --git a/test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java b/test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java deleted file mode 100644 index 404580a88..000000000 --- a/test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tradefed.device.metric; - -import com.android.annotations.VisibleForTesting; -import com.android.loganalysis.item.CompactMemInfoItem; -import com.android.loganalysis.parser.CompactMemInfoParser; -import com.android.tradefed.config.Option; -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.result.FileInputStreamSource; -import com.android.tradefed.result.LogDataType; -import com.android.tradefed.util.FileUtil; -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * A {@link ScheduledDeviceMetricCollector} to collect memory dumps of processes at regular - * intervals. - */ -public class DumpHeapCollector extends ScheduledDeviceMetricCollector { - private static final String DUMPHEAP_OUTPUT = "/data/local/tmp"; - private static final String SUFFIX = "trigger"; - - @Option( - name = "dumpheap-thresholds", - description = - "Threshold map for taking process dumpheaps. " - + "The key should be the process name and its corresponding value is the " - + "maximum acceptable heap size for that process." - + "Note that to get heap dump for native and managed processes set their " - + "threshold to 0." - ) - protected Map<String, Long> mDumpheapThresholds = new HashMap<String, Long>(); - - @Override - public void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { - CLog.i("Running dumpheap collection..."); - List<File> dumpFiles = new ArrayList<>(); - try { - for (String process : mDumpheapThresholds.keySet()) { - String output = - device.executeShellCommand( - String.format("dumpsys meminfo -c | grep %s", process)); - - dumpFiles = takeDumpheap(device, output, process, mDumpheapThresholds.get(process)); - - dumpFiles.forEach(dumpheap -> saveDumpheap(dumpheap)); - } - } catch (DeviceNotAvailableException e) { - CLog.e(e); - } finally { - dumpFiles.forEach(dumpFile -> FileUtil.deleteFile(dumpFile)); - } - } - - /** - * Collects heap dump for each requested process if the PSS is greater than a threshold. - * - * @param device - * @param output of the meminfo command. - * @param process for which we need the heap dump. - * @param threshold which is the maximum tolerable PSS. - * @return the list of {@link File}s in the host containing the report. Empty list if something - * failed. - * @throws DeviceNotAvailableException - */ - @VisibleForTesting - List<File> takeDumpheap(ITestDevice device, String output, String process, Long threshold) - throws DeviceNotAvailableException { - List<File> dumpFiles = new ArrayList<>(); - if (output.isEmpty()) { - CLog.i("Skipping %s -- no process found.", process); - return dumpFiles; - } - - CompactMemInfoItem item = - new CompactMemInfoParser().parse(Arrays.asList(output.split("\n"))); - - for (Integer pid : item.getPids()) { - if (item.getName(pid).equals(process) && item.getPss(pid) > threshold) { - File dump = device.dumpHeap(process, getDevicePath(process)); - dumpFiles.add(dump); - } - } - return dumpFiles; - } - - /** - * Returns the path on the device to put the dump. - * - * @param process for which dump is being requested. - * @return a write-able path in device. - */ - private String getDevicePath(String process) { - return String.format( - "%s/%s_%s_%s.hprof", DUMPHEAP_OUTPUT, process, SUFFIX, getFileSuffix()); - } - - @VisibleForTesting - void saveDumpheap(File dumpheap) { - if (dumpheap == null) { - CLog.e("Failed to take dumpheap."); - return; - } - try (FileInputStreamSource stream = new FileInputStreamSource(dumpheap)) { - getInvocationListener() - .testLog(FileUtil.getBaseName(dumpheap.getName()), LogDataType.HPROF, stream); - } - } -} diff --git a/test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java b/test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java deleted file mode 100644 index 8e73edac3..000000000 --- a/test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.result.FileInputStreamSource; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; -import com.google.common.io.Files; -import java.io.File; -import java.io.IOException; - -/** A {@link ScheduledDeviceMetricCollector} to collect graphics stats at regular intervals. */ -public class GraphicsStatsMetricCollector extends ScheduledDeviceMetricCollector { - GraphicsStatsMetricCollector() { - setTag("jank"); - } - - @Override - public void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { - try { - CLog.i("Running graphicsstats..."); - String outputFileName = - String.format("%s/graphics-%s", createTempDir(), getFileSuffix()); - File outputFile = saveProcessOutput(device, "dumpsys graphicsstats", outputFileName); - try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) { - getInvocationListener() - .testLog( - Files.getNameWithoutExtension(outputFile.getName()), - LogDataType.GFX_INFO, - source); - } - } catch (DeviceNotAvailableException | IOException e) { - CLog.e(e); - } - } -} diff --git a/test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java deleted file mode 100644 index 3312855db..000000000 --- a/test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.result.FileInputStreamSource; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; -import com.google.common.io.Files; -import java.io.File; -import java.io.IOException; - -/** - * A {@link ScheduledDeviceMetricCollector} to collect audio and system memory heaps at regular - * intervals. - */ -public class IonHeapInfoMetricCollector extends ScheduledDeviceMetricCollector { - public IonHeapInfoMetricCollector() { - setTag("ion"); - } - - @Override - void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { - collectIonAudio(device); - collectIonSystem(device); - } - - private void collectIonAudio(ITestDevice device) { - try { - CLog.i("Running ionheap audio collector..."); - String outputFileName = - String.format("%s/ion-audio-%s", createTempDir(), getFileSuffix()); - File outputFile = saveProcessOutput(device, "cat /d/ion/heaps/audio", outputFileName); - try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) { - getInvocationListener() - .testLog( - Files.getNameWithoutExtension(outputFile.getName()), - LogDataType.TEXT, - source); - } - } catch (DeviceNotAvailableException | IOException e) { - CLog.e(e); - } - } - - private void collectIonSystem(ITestDevice device) { - try { - CLog.i("Running ionheap system collector..."); - String outputFileName = - String.format("%s/ion-system-%s", createTempDir(), getFileSuffix()); - File outputFile = saveProcessOutput(device, "cat /d/ion/heaps/system", outputFileName); - try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) { - getInvocationListener() - .testLog( - Files.getNameWithoutExtension(outputFile.getName()), - LogDataType.TEXT, - source); - } - } catch (DeviceNotAvailableException | IOException e) { - CLog.e(e); - } - } -} diff --git a/test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java deleted file mode 100644 index 0890b7408..000000000 --- a/test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.result.FileInputStreamSource; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; -import com.google.common.io.Files; -import java.io.File; -import java.io.IOException; - -/** A {@link ScheduledDeviceMetricCollector} to collect memory dumps at regular intervals. */ -public class MemInfoMetricCollector extends ScheduledDeviceMetricCollector { - MemInfoMetricCollector() { - setTag("compact-meminfo"); - } - - @Override - public void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { - try { - CLog.i("Running meminfo..."); - String outputFileName = - String.format("%s/compact-meminfo-%s", createTempDir(), getFileSuffix()); - File outputFile = saveProcessOutput(device, "dumpsys meminfo -c -S", outputFileName); - try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) { - getInvocationListener() - .testLog( - Files.getNameWithoutExtension(outputFile.getName()), - LogDataType.COMPACT_MEMINFO, - source); - } - } catch (DeviceNotAvailableException | IOException e) { - CLog.e(e); - } - } -} diff --git a/test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java deleted file mode 100644 index 9ab0f3381..000000000 --- a/test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.result.FileInputStreamSource; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; -import com.google.common.io.Files; -import java.io.File; -import java.io.IOException; - -/** A {@link ScheduledDeviceMetricCollector} to collect free page counts at regular intervals. */ -public class PagetypeInfoMetricCollector extends ScheduledDeviceMetricCollector { - public PagetypeInfoMetricCollector() { - setTag("pagetypeinfo"); - } - - @Override - void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { - try { - CLog.i("Running pagetype info collector..."); - String outputFileName = - String.format("%s/pagetypeinfo-%s", createTempDir(), getFileSuffix()); - File outputFile = saveProcessOutput(device, "cat /proc/pagetypeinfo", outputFileName); - try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) { - getInvocationListener() - .testLog( - Files.getNameWithoutExtension(outputFile.getName()), - LogDataType.TEXT, - source); - } - } catch (DeviceNotAvailableException | IOException e) { - CLog.e(e); - } - } -} diff --git a/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java b/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java index 9e5662610..4d496a411 100644 --- a/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java +++ b/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java @@ -144,6 +144,11 @@ public class PerfettoPullerMetricCollector extends FilePullerDeviceMetricCollect description = "Convert the raw trace file to perfetto metric file.") private boolean mConvertToMetricFile = true; + @Option(name = "collect-perfetto-file-size", + description = "Set it to true to collect the perfetto file size as part" + + " of the metrics.") + private boolean mCollectPerfettoFileSize = false; + @Option( name = "trace-processor-binary", description = "Path to the trace processor shell. This will" @@ -193,7 +198,7 @@ public class PerfettoPullerMetricCollector extends FilePullerDeviceMetricCollect } // Update the file size metrics. - if (processSrcFile != null) { + if (processSrcFile != null && mCollectPerfettoFileSize) { double perfettoFileSizeInBytes = processSrcFile.length(); Metric.Builder metricDurationBuilder = Metric.newBuilder(); metricDurationBuilder.getMeasurementsBuilder().setSingleDouble( diff --git a/test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java b/test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java deleted file mode 100644 index 55281776b..000000000 --- a/test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import com.android.loganalysis.item.DumpsysProcessMeminfoItem; -import com.android.loganalysis.parser.DumpsysProcessMeminfoParser; -import com.android.tradefed.config.Option; -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.metrics.proto.MetricMeasurement.DataType; -import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements; -import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; -import com.android.tradefed.metrics.proto.MetricMeasurement.NumericValues; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -/** - * A {@link ScheduledDeviceMetricCollector} to measure peak memory usage of specified processes. - * Collects PSS and USS (private dirty) memory usage values from dumpsys meminfo. The result will be - * reported as a test run metric with key in the form of PSS#ProcName[#DeviceNum], in KB. - */ -public class ProcessMaxMemoryCollector extends ScheduledDeviceMetricCollector { - - @Option( - name = "memory-usage-process-name", - description = "Process names (from `dumpsys meminfo`) to measure memory usage for" - ) - private List<String> mProcessNames = new ArrayList<>(); - - private class DeviceMemoryData { - /** Peak PSS per process */ - private Map<String, Long> mProcPss = new HashMap<>(); - /** Peak USS per process */ - private Map<String, Long> mProcUss = new HashMap<>(); - } - - // Memory usage data per device - private Map<ITestDevice, DeviceMemoryData> mMemoryData; - private Map<ITestDevice, Map<String, NumericValues.Builder>> mPssMemoryPerProcess; - private Map<ITestDevice, Map<String, NumericValues.Builder>> mUssMemoryPerProcess; - - @Override - void onStart(DeviceMetricData runData) { - mMemoryData = new HashMap<>(); - mPssMemoryPerProcess = new HashMap<>(); - mUssMemoryPerProcess = new HashMap<>(); - - for (ITestDevice device : getDevices()) { - mMemoryData.put(device, new DeviceMemoryData()); - mPssMemoryPerProcess.put(device, new HashMap<>()); - mUssMemoryPerProcess.put(device, new HashMap<>()); - } - } - - @Override - void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { - try { - Map<String, Long> procPss = mMemoryData.get(device).mProcPss; - Map<String, Long> procUss = mMemoryData.get(device).mProcUss; - for (String proc : mProcessNames) { - String dumpResult = device.executeShellCommand("dumpsys meminfo --checkin " + proc); - if (dumpResult.startsWith("No process found")) { - // process not found, skip - continue; - } - DumpsysProcessMeminfoItem item = - new DumpsysProcessMeminfoParser() - .parse(Arrays.asList(dumpResult.split("\n"))); - Long pss = - item.get(DumpsysProcessMeminfoItem.TOTAL) - .get(DumpsysProcessMeminfoItem.PSS); - Long uss = - item.get(DumpsysProcessMeminfoItem.TOTAL) - .get(DumpsysProcessMeminfoItem.PRIVATE_DIRTY); - if (pss == null || uss == null) { - CLog.e("Error parsing meminfo output: " + dumpResult); - continue; - } - - // Track PSS values - if (mPssMemoryPerProcess.get(device) == null) { - mPssMemoryPerProcess.put(device, new HashMap<>()); - } - if (mPssMemoryPerProcess.get(device).get(proc) == null) { - mPssMemoryPerProcess.get(device).put(proc, NumericValues.newBuilder()); - } - mPssMemoryPerProcess.get(device).get(proc).addNumericValue(pss); - - // Track USS values - if (mUssMemoryPerProcess.get(device) == null) { - mUssMemoryPerProcess.put(device, new HashMap<>()); - } - if (mUssMemoryPerProcess.get(device).get(proc) == null) { - mUssMemoryPerProcess.get(device).put(proc, NumericValues.newBuilder()); - } - mUssMemoryPerProcess.get(device).get(proc).addNumericValue(uss); - - if (procPss.getOrDefault(proc, 0L) < pss) { - procPss.put(proc, pss); - } - if (procUss.getOrDefault(proc, 0L) < uss) { - procUss.put(proc, uss); - } - } - } catch (DeviceNotAvailableException e) { - CLog.e(e); - } - } - - @Override - void onEnd(DeviceMetricData runData) { - for (ITestDevice device : getDevices()) { - // Report all the PSS data for each process - for (Entry<String, NumericValues.Builder> values : - mPssMemoryPerProcess.get(device).entrySet()) { - Metric.Builder metric = Metric.newBuilder(); - metric.setMeasurements( - Measurements.newBuilder() - .setNumericValues(values.getValue().build())) - .build(); - metric.setUnit("kB").setType(DataType.RAW); - runData.addMetricForDevice(device, "PSS#" + values.getKey(), metric); - } - - // Report all the USS data for each process - for (Entry<String, NumericValues.Builder> values : - mUssMemoryPerProcess.get(device).entrySet()) { - Metric.Builder metric = Metric.newBuilder(); - metric.setMeasurements( - Measurements.newBuilder() - .setNumericValues(values.getValue().build())) - .build(); - metric.setUnit("kB").setType(DataType.RAW); - runData.addMetricForDevice(device, "USS#" + values.getKey(), metric); - } - - // Continue reporting the max PSS / USS for compatibility - Map<String, Long> procPss = mMemoryData.get(device).mProcPss; - Map<String, Long> procUss = mMemoryData.get(device).mProcUss; - for (Entry<String, Long> pss : procPss.entrySet()) { - Metric.Builder metric = Metric.newBuilder(); - metric.setMeasurements( - Measurements.newBuilder().setSingleInt(pss.getValue()).build()); - metric.setUnit("kB").setType(DataType.PROCESSED); - runData.addMetricForDevice(device, "MAX_PSS#" + pss.getKey(), metric); - } - for (Entry<String, Long> uss : procUss.entrySet()) { - Metric.Builder metric = Metric.newBuilder(); - metric.setMeasurements( - Measurements.newBuilder().setSingleInt(uss.getValue()).build()); - metric.setUnit("kB").setType(DataType.PROCESSED); - runData.addMetricForDevice(device, "MAX_USS#" + uss.getKey(), metric); - } - } - } -} diff --git a/test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java deleted file mode 100644 index 347a4b903..000000000 --- a/test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tradefed.device.metric; - -import com.android.tradefed.config.Option; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; -import com.android.tradefed.result.ITestInvocationListener; - -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; - -/** - * A {@link IMetricCollector} that makes runs multiple metric collectors periodically. This is a - * best effort scheduler. It makes the best effort to run the collectors at given intervals while - * making sure that no two collectors are run at the same time. - */ -public class ScheduleMultipleDeviceMetricCollector extends BaseDeviceMetricCollector { - @Option( - name = "metric-collection-intervals", - description = "The interval at which the collectors should run." - ) - private Map<String, Long> mIntervalMs = new HashMap<>(); - - @Option( - name = "metric-storage-path", - description = - "Absolute path to a directory on host where the collected metrics will be stored." - ) - private File mMetricStoragePath = new File(System.getProperty("java.io.tmpdir")); - - @Option( - name = "metric-collector-command-classes", - description = - "Complete package name of a class which registers the commands to do the actual " - + "job of collection. Can be repeated." - ) - private List<String> mMetricCollectorClasses = new ArrayList<>(); - - // List of collectors to run. - private List<ScheduledDeviceMetricCollector> mMetricCollectors = new ArrayList<>(); - - // Time interval at which the commands should run. - private Map<ScheduledDeviceMetricCollector, Long> mMetricCollectorIntervals = new HashMap<>(); - - // Time when the commands to collect various metrics were last run. - private Map<ScheduledDeviceMetricCollector, Long> mLastUpdate = new HashMap<>(); - - private Timer mTimer; - - private long mScheduleRate; - - @Override - public ITestInvocationListener init( - IInvocationContext context, ITestInvocationListener listener) { - super.init(context, listener); - initMetricCollectors(context, listener); - - return this; - } - - /** Gets an instance of all the requested metric collectors. */ - private void initMetricCollectors( - IInvocationContext context, ITestInvocationListener listener) { - for (String metricCollectorClass : mMetricCollectorClasses) { - try { - Class<?> klass = Class.forName(metricCollectorClass); - - ScheduledDeviceMetricCollector singleMetricCollector = - klass.asSubclass(ScheduledDeviceMetricCollector.class) - .getDeclaredConstructor() - .newInstance(); - - singleMetricCollector.init(context, listener); - - mMetricCollectors.add(singleMetricCollector); - } catch (ClassNotFoundException - | InstantiationException - | IllegalAccessException - | InvocationTargetException - | NoSuchMethodException e) { - CLog.e("Class %s not found, skipping.", metricCollectorClass); - CLog.e(e); - } - } - } - - @Override - public final void onTestRunStart(DeviceMetricData runData) { - if (mMetricCollectorClasses.isEmpty()) { - CLog.w("No single metric class provided. Skipping collection."); - return; - } - - setupCollection(); - - if (mScheduleRate == 0) { - CLog.e( - "Failed to get a valid interval for even one metric collector. " - + "Please make sure that the collectors have non-zero intervals " - + "specified as an argument to this class."); - return; - } - - // TODO(b/70394486): Investigate if ScheduledThreadPool is better suited here so that we can - // schedule all the metrics in their own thread and create a common object which allows - // running of only one collector at a time. - mTimer = new Timer(); - - TimerTask timerTask = - new TimerTask() { - @Override - public void run() { - collect(runData); - } - }; - - mTimer.scheduleAtFixedRate(timerTask, 0, mScheduleRate); - } - - /** - * Sets up the collection process by parsing all the args, retrieving the intervals from the - * args and initializing the last update value of each of the collectors to current time. - */ - private void setupCollection() { - parseAllArgs(); - for (ScheduledDeviceMetricCollector singleMetricCollector : - mMetricCollectorIntervals.keySet()) { - mLastUpdate.put(singleMetricCollector, System.currentTimeMillis()); - } - - mScheduleRate = gcdOfIntervals(); - } - - /** - * Runs all the requested collectors sequentially. Dumps the output in {@code - * mmResultsDirectory/outputDirFormat} of the collector prefixed. - * - * @param runData holds the filename of the metrics collected for each collector. - */ - private void collect(DeviceMetricData runData) { - for (ScheduledDeviceMetricCollector singleMetricCollector : - mMetricCollectorIntervals.keySet()) { - - Long elapsedTime = System.currentTimeMillis() - mLastUpdate.get(singleMetricCollector); - - Long taskInterval = mMetricCollectorIntervals.get(singleMetricCollector); - - if (elapsedTime >= taskInterval) { - try { - for (ITestDevice device : getDevices()) { - singleMetricCollector.collect(device, runData); - } - mLastUpdate.put(singleMetricCollector, System.currentTimeMillis()); - } catch (InterruptedException e) { - CLog.e("Exception during %s", singleMetricCollector.getClass()); - CLog.e(e); - } - } - } - } - - /** Parse all the intervals provided in the command line. */ - private void parseAllArgs() { - for (ScheduledDeviceMetricCollector metricCollector : mMetricCollectors) { - Long value = mIntervalMs.getOrDefault(metricCollector.getTag(), 0L); - - if (value > 0) { - mMetricCollectorIntervals.put(metricCollector, value); - } else if (value < 0) { - throw new IllegalArgumentException( - metricCollector.getClass() + " expects a non negative interval."); - } - } - } - - /** Get the {@code scheduleRate} common to all tasks which is the gcd of all the intervals. */ - private Long gcdOfIntervals() { - Collection<Long> intervals = mMetricCollectorIntervals.values(); - if (intervals.isEmpty()) { - return 0L; - } - BigInteger gcdSoFar = new BigInteger(intervals.iterator().next().toString()); - - for (Long interval : intervals) { - gcdSoFar = gcdSoFar.gcd(new BigInteger(interval.toString())); - } - - return gcdSoFar.longValue(); - } - - @Override - public final void onTestRunEnd( - DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) { - if (mTimer != null) { - mTimer.cancel(); - mTimer.purge(); - } - } -} diff --git a/test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java deleted file mode 100644 index 409ac2b77..000000000 --- a/test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import com.android.annotations.VisibleForTesting; -import com.android.tradefed.config.Option; -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; -import com.android.tradefed.util.FileUtil; - -import java.io.File; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; - -/** - * A {@link IMetricCollector} that allows to run a collection task periodically at a set interval. - */ -public abstract class ScheduledDeviceMetricCollector extends BaseDeviceMetricCollector { - - @Option( - name = "fixed-schedule-rate", - description = "Schedule the timetask as a fixed schedule rate" - ) - private boolean mFixedScheduleRate = false; - - @Option( - name = "interval", - description = "the interval between two tasks being scheduled", - isTimeVal = true - ) - private long mIntervalMs = 60 * 1000l; - - private Timer timer; - - @Override - public final void onTestRunStart(DeviceMetricData runData) { - CLog.d("starting with interval = %s", mIntervalMs); - onStart(runData); - timer = new Timer(); - TimerTask timerTask = - new TimerTask() { - @Override - public void run() { - try { - for (ITestDevice device : getDevices()) { - collect(device, runData); - } - } catch (InterruptedException e) { - timer.cancel(); - Thread.currentThread().interrupt(); - CLog.e("Interrupted exception thrown from task:"); - CLog.e(e); - } - } - }; - - if (mFixedScheduleRate) { - timer.scheduleAtFixedRate(timerTask, 0, mIntervalMs); - } else { - timer.schedule(timerTask, 0, mIntervalMs); - } - } - - @Override - public final void onTestRunEnd( - DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) { - if (timer != null) { - timer.cancel(); - timer.purge(); - } - onEnd(runData); - CLog.d("finished"); - } - - /** - * Task periodically & asynchronously run during the test running on a specific device. - * - * @param device the {@link ITestDevice} the metric is associated to. - * @param runData the {@link DeviceMetricData} where to put metrics. - * @throws InterruptedException - */ - abstract void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException; - - /** - * Executed when entering this collector. - * - * @param runData the {@link DeviceMetricData} where to put metrics. - */ - void onStart(DeviceMetricData runData) { - // Does nothing. - } - - /** - * Executed when finishing this collector. - * - * @param runData the {@link DeviceMetricData} where to put metrics. - */ - void onEnd(DeviceMetricData runData) { - // Does nothing. - } - - /** - * Send all the output of a process from all the devices to a file. - * - * <p>Please note, metric collections should not overlap. - * - * @throws DeviceNotAvailableException - * @throws IOException - */ - File saveProcessOutput(ITestDevice device, String command, String outputFileName) - throws DeviceNotAvailableException, IOException { - String output = device.executeShellCommand(command); - - // Create the output file and dump the output of the command to this file. - File outputFile = new File(outputFileName); - - FileUtil.writeToFile(output, outputFile); - - return outputFile; - } - - /** - * Create a suffix string to be appended at the end of each metric file to keep the name unique - * at each run. - * - * @return suffix string in the format year-month-date-hour-minute-seconds-milliseconds. - */ - @VisibleForTesting - String getFileSuffix() { - return new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US).format(new Date()); - } - - /** - * Creates temporary directory to store the metric files. - * - * @return {@link File} directory with 'tmp' prefixed to its name to signify that its temporary. - * @throws IOException - */ - @VisibleForTesting - File createTempDir() throws IOException { - return FileUtil.createTempDir(String.format("tmp_%s", getTag())); - } -} diff --git a/test_framework/com/android/tradefed/device/metric/TemperatureCollector.java b/test_framework/com/android/tradefed/device/metric/TemperatureCollector.java deleted file mode 100644 index 2c38cd55d..000000000 --- a/test_framework/com/android/tradefed/device/metric/TemperatureCollector.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static com.android.tradefed.targetprep.TemperatureThrottlingWaiter.DEVICE_TEMPERATURE_FILE_PATH_NAME; - -import com.android.tradefed.config.Option; -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.metrics.proto.MetricMeasurement.DataType; -import com.android.tradefed.metrics.proto.MetricMeasurement.DoubleValues; -import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements; -import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; - -import java.util.HashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A {@link ScheduledDeviceMetricCollector} to measure min and max device temperature. Useful for - * long duration performance tests to monitor if the device overheats. - */ -public class TemperatureCollector extends ScheduledDeviceMetricCollector { - - private static final String CELCIUS_UNIT = "celcius"; - - // Option name intentionally shared with TemperatureThrottlingWaiter - @Option( - name = DEVICE_TEMPERATURE_FILE_PATH_NAME, - description = - "Name of file that contains device temperature. " - + "Example: /sys/class/hwmon/hwmon1/device/msm_therm" - ) - private String mDeviceTemperatureFilePath = null; - - @Option( - name = "device-temperature-file-regex", - description = - "Regex to parse temperature file. First group must be the temperature parsable" - + "to Double. Default: Result:(\\d+) Raw:.*" - ) - private String mDeviceTemperatureFileRegex = "Result:(\\d+) Raw:.*"; - - /** - * Stores the highest recorded temperature per device. Device will not be present in the map if - * no valid temperature was recorded. - */ - private Map<ITestDevice, Double> mMaxDeviceTemps; - - /** - * Stores the lowest recorded temperature per device. Device will not be present in the map if - * no valid temperature was recorded. - */ - private Map<ITestDevice, Double> mMinDeviceTemps; - - // Example: Result:32 Raw:7e51 - private static Pattern mTemperatureRegex; - - private Map<ITestDevice, DoubleValues.Builder> mValues; - - @Override - void onStart(DeviceMetricData runData) { - mTemperatureRegex = Pattern.compile(mDeviceTemperatureFileRegex); - mMaxDeviceTemps = new HashMap<>(); - mMinDeviceTemps = new HashMap<>(); - mValues = new HashMap<>(); - } - - @Override - void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { - if (mDeviceTemperatureFilePath == null) { - return; - } - try { - if (!device.isAdbRoot()) { - return; - } - Double temp = getTemperature(device); - if (temp == null) { - return; - } - if (mValues.get(device) == null) { - mValues.put(device, DoubleValues.newBuilder()); - } - mValues.get(device).addDoubleValue(temp); - mMaxDeviceTemps.putIfAbsent(device, temp); - mMinDeviceTemps.putIfAbsent(device, temp); - if (mMaxDeviceTemps.get(device) < temp) { - mMaxDeviceTemps.put(device, temp); - } - if (mMinDeviceTemps.get(device) > temp) { - mMinDeviceTemps.put(device, temp); - } - } catch (DeviceNotAvailableException e) { - CLog.e(e); - } - } - - private Double getTemperature(ITestDevice device) throws DeviceNotAvailableException { - String cmd = "cat " + mDeviceTemperatureFilePath; - String result = device.executeShellCommand(cmd).trim(); - Matcher m = mTemperatureRegex.matcher(result); - if (m.matches()) { - return Double.parseDouble(m.group(1)); - } - CLog.e("Error parsing temperature file output: " + result); - return null; - } - - @Override - void onEnd(DeviceMetricData runData) { - for (ITestDevice device : getDevices()) { - DoubleValues.Builder values = mValues.get(device); - if (values != null) { - Metric.Builder metric = Metric.newBuilder(); - metric.setMeasurements( - Measurements.newBuilder().setDoubleValues(values.build()).build()); - metric.setUnit(CELCIUS_UNIT).setType(DataType.RAW); - runData.addMetricForDevice(device, "temperature", metric); - } - // Report the max and min for compatibility - Double maxTemp = mMaxDeviceTemps.get(device); - if (maxTemp != null) { - Metric.Builder metric = Metric.newBuilder(); - metric.setMeasurements(Measurements.newBuilder().setSingleDouble(maxTemp).build()); - // Since we report some processed value report it as PROCESSED. - metric.setUnit(CELCIUS_UNIT).setType(DataType.PROCESSED); - runData.addMetricForDevice(device, "max_temperature", metric); - } - Double minTemp = mMinDeviceTemps.get(device); - if (minTemp != null) { - Metric.Builder metric = Metric.newBuilder(); - metric.setMeasurements(Measurements.newBuilder().setSingleDouble(minTemp).build()); - // Since we report some processed value report it as PROCESSED. - metric.setUnit(CELCIUS_UNIT).setType(DataType.PROCESSED); - runData.addMetricForDevice(device, "min_temperature", metric); - } - } - } -} diff --git a/test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java deleted file mode 100644 index f1cc72f1b..000000000 --- a/test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; -import com.android.tradefed.result.FileInputStreamSource; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; -import com.google.common.io.Files; -import java.io.File; -import java.io.IOException; - -/** A {@link ScheduledDeviceMetricCollector} to collect kernel debug trace at regular intervals. */ -public class TraceMetricCollector extends ScheduledDeviceMetricCollector { - TraceMetricCollector() { - setTag("trace"); - } - - @Override - void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { - try { - CLog.i("Running trace collector..."); - String outputFileName = String.format("%s/trace-%s", createTempDir(), getFileSuffix()); - File outputFile = - saveProcessOutput( - device, "cat /sys/kernel/debug/tracing/trace", outputFileName); - try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) { - getInvocationListener() - .testLog( - Files.getNameWithoutExtension(outputFile.getName()), - LogDataType.TEXT, - source); - } - } catch (DeviceNotAvailableException | IOException e) { - CLog.e(e); - } - } -} diff --git a/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java index 618b6bc8d..cd8033292 100644 --- a/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java +++ b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java @@ -344,7 +344,14 @@ public class PerfettoGenericPostProcessor extends BasePostProcessor { for (Entry<FieldDescriptor, Object> entry : fields.entrySet()) { if (!(entry.getValue() instanceof Message) && !(entry.getValue() instanceof List)) { if (isNumeric(entry.getValue().toString())) { - // Construct the metric if it is numeric value. + // Check if the current field has to be used as prefix for other fields + // and add it to the list of prefixes. + if (mPerfettoPrefixKeyFields.contains(entry.getKey().toString())) { + keyPrefixOtherFields.add(String.format("%s-%s", + entry.getKey().getName().toString(), entry.getValue().toString())); + continue; + } + // Otherwise treat this numeric field as metric. if (mNumberPattern.matcher(entry.getValue().toString()).matches()) { convertedMetrics.put( entry.getKey().getName(), @@ -355,9 +362,9 @@ public class PerfettoGenericPostProcessor extends BasePostProcessor { convertedMetrics.put( entry.getKey().getName(), TfMetricProtoUtil.stringToMetric( - Long.toString( - Double.valueOf(entry.getValue().toString()) - .longValue())) + Long.toString( + Double.valueOf(entry.getValue().toString()) + .longValue())) .toBuilder()); } } else { @@ -375,20 +382,6 @@ public class PerfettoGenericPostProcessor extends BasePostProcessor { } } - // Add prefix key to all the keys in current proto message which has numeric values. - Map<String, Metric.Builder> additionalConvertedMetrics = - new HashMap<String, Metric.Builder>(); - for (String prefix : keyPrefixOtherFields) { - for (Map.Entry<String, Metric.Builder> currentMetric : convertedMetrics.entrySet()) { - additionalConvertedMetrics.put(String.format("%s-%s", prefix, - currentMetric.getKey()), currentMetric.getValue()); - } - } - - // Not cleaning up the other metrics without prefix fields. - convertedMetrics.putAll(additionalConvertedMetrics); - - // Recursively expand the proto messages and repeated fields(i.e list). // Recursion when there are no messages or list with in the current message. for (Entry<FieldDescriptor, Object> entry : fields.entrySet()) { @@ -458,6 +451,20 @@ public class PerfettoGenericPostProcessor extends BasePostProcessor { } } } + + // Add prefix key to all the keys in current proto message which has numeric values. + Map<String, Metric.Builder> additionalConvertedMetrics = + new HashMap<String, Metric.Builder>(); + for (String prefix : keyPrefixOtherFields) { + for (Map.Entry<String, Metric.Builder> currentMetric : convertedMetrics.entrySet()) { + additionalConvertedMetrics.put(String.format("%s-%s", prefix, + currentMetric.getKey()), currentMetric.getValue()); + } + } + + // Not cleaning up the other metrics without prefix fields. + convertedMetrics.putAll(additionalConvertedMetrics); + return convertedMetrics; } @@ -511,3 +518,4 @@ public class PerfettoGenericPostProcessor extends BasePostProcessor { return mProcessedMetric ? DataType.PROCESSED : DataType.RAW; } } + diff --git a/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java b/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java new file mode 100644 index 000000000..0d32890a1 --- /dev/null +++ b/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tradefed.targetprep; + +import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.build.IDeviceBuildInfo; +import com.android.tradefed.command.remote.DeviceDescriptor; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.invoker.TestInformation; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; +import com.android.tradefed.util.FileUtil; + +import java.io.File; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; + +/** Create chroot directory for ART tests. */ +@OptionClass(alias = "art-chroot-preparer") +public class ArtChrootPreparer extends BaseTargetPreparer { + + // Predefined location of the chroot root directory. + public static final String CHROOT_PATH = "/data/local/tmp/art-test-chroot"; + + // Directories to create in the chroot. + private static final String[] MKDIRS = { + "/", "/apex", "/data", "/data/dalvik-cache", "/data/local/tmp", "/tmp", + }; + + // System mount points to replicate in the chroot. + private static final String[] MOUNTS = { + "/dev", "/linkerconfig", "/proc", "/sys", "/system", "/apex/com.android.os.statsd", + }; + + @Override + public void setUp(TestInformation testInfo) + throws TargetSetupError, BuildError, DeviceNotAvailableException { + ITestDevice device = testInfo.getDevice(); + + // Ensure there are no files left from previous runs. + cleanup(device); + + // Create directories required for ART testing in chroot. + for (String dir : MKDIRS) { + adbShell(device, "mkdir -p " + CHROOT_PATH + dir); + } + + // Replicate system mount point in the chroot. + for (String dir : MOUNTS) { + adbShell(device, "mkdir -p " + CHROOT_PATH + dir); + adbShell(device, "mount --bind " + dir + " " + CHROOT_PATH + dir); + } + + // Activate APEXes in the chroot. + IBuildInfo buildInfo = testInfo.getBuildInfo(); + IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo) buildInfo; + DeviceDescriptor deviceDesc = device.getDeviceDescriptor(); + File tests_dir = deviceBuild.getFile(BuildInfoFileKey.TARGET_LINKED_DIR); + // The art_chroot is a shared module containing comment ART test data. + File apexes_dir = FileUtil.getFileForPath(tests_dir, "art_chroot", "system", "apex"); + if (apexes_dir.listFiles() == null) { + throw new TargetSetupError( + "No apex files found in " + apexes_dir.getPath(), deviceDesc); + } + File tempDir = null; + try { + tempDir = FileUtil.createTempDir("art-test-apex"); + for (File apex : apexes_dir.listFiles()) { + activateApex(device, tempDir, apex); + } + } catch (IOException e) { + throw new TargetSetupError("Error when activating apex", e, deviceDesc); + } finally { + FileUtil.recursiveDelete(tempDir); + } + } + + private void activateApex(ITestDevice device, File tempDir, File apex) + throws TargetSetupError, IOException, DeviceNotAvailableException { + CLog.i("Activate apex in ART chroot: " + apex.getName()); + ZipFile apex_zip = new ZipFile(apex); + ZipArchiveEntry apex_payload = apex_zip.getEntry("apex_payload.img"); + File temp = FileUtil.createTempFile("payload-", ".img", tempDir); + FileUtil.writeToFile(apex_zip.getInputStream(apex_payload), temp); + String deviceApexDir = CHROOT_PATH + "/apex/" + apex.getName(); + // Rename "com.android.art.testing.apex" to just "com.android.art.apex". + deviceApexDir = deviceApexDir.replace(".testing.apex", "").replace(".apex", ""); + String deviceApexImg = deviceApexDir + ".img"; + if (!device.pushFile(temp, deviceApexImg)) { + throw new TargetSetupError( + "adb push failed for " + apex.getName(), device.getDeviceDescriptor()); + } + // TODO(b/168048638): Work-around for cuttlefish: first losetup call always fails. + device.executeShellV2Command("losetup -f"); + // Mount the apex file via a loopback device. + String loopbackDevice = adbShell(device, "losetup -f -s " + deviceApexImg); + adbShell(device, "mkdir -p " + deviceApexDir); + adbShell(device, "mount -o loop,ro " + loopbackDevice + " " + deviceApexDir); + } + + @Override + public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException { + try { + cleanup(testInfo.getDevice()); + } catch (TargetSetupError ex) { + CLog.e("Tear-down failed: " + ex.toString()); + } + } + + // Wrapper for executeShellV2Command that checks that the command succeeds. + private String adbShell(ITestDevice device, String cmd) + throws TargetSetupError, DeviceNotAvailableException { + CommandResult result = device.executeShellV2Command(cmd); + if (result.getStatus() != CommandStatus.SUCCESS) { + throw new TargetSetupError( + String.format( + "adb shell command failed: '%s': %s".format(cmd, result.getStderr()))); + } + return result.getStdout(); + } + + private void cleanup(ITestDevice device) throws TargetSetupError, DeviceNotAvailableException { + String mounts = adbShell(device, "mount"); + Pattern pattern = Pattern.compile("^([^ ]+) on ([^ ]+) type ([^ ]+) .*$"); + for (String mount : mounts.split("\n")) { + Matcher matcher = pattern.matcher(mount); + if (!matcher.matches()) { + throw new TargetSetupError("Failed to parse mount command output: " + mount); + } + if (matcher.group(2).startsWith(CHROOT_PATH)) { + adbShell(device, "umount " + matcher.group(2)); + } + } + adbShell(device, "rm -rf " + CHROOT_PATH); + } +} diff --git a/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java b/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java index 1c837da3d..d54cb4934 100644 --- a/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java +++ b/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java @@ -26,11 +26,14 @@ import com.android.tradefed.result.error.InfraErrorIdentifier; import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; import com.android.tradefed.util.FileUtil; +import com.android.tradefed.util.SparseImageUtil; import com.android.tradefed.util.ZipUtil; import com.android.tradefed.util.ZipUtil2; + +import org.apache.commons.compress.archivers.zip.ZipFile; + import java.io.File; import java.io.IOException; -import org.apache.commons.compress.archivers.zip.ZipFile; /** * An {@link ITargetPreparer} that sets up a system image on top of a device build with the Dynamic @@ -43,11 +46,15 @@ public class DynamicSystemPreparer extends BaseTargetPreparer { private static final String DEST_PATH = "/sdcard/system.raw.gz"; @Option( - name = "system-image-zip-name", - description = "The name of the zip file containing system.img." - ) + name = "system-image-zip-name", + description = "The name of the zip file containing system.img.") private String mSystemImageZipName = "system-img.zip"; + @Option( + name = "user-data-size-in-gb", + description = "Number of GB to be allocated for DSU user-data.") + private long mUserDataSizeInGb = 16L; // 16GB + private boolean isDSURunning(ITestDevice device) throws DeviceNotAvailableException { CollectingOutputReceiver receiver = new CollectingOutputReceiver(); device.executeShellCommand("gsi_tool status", receiver); @@ -67,15 +74,21 @@ public class DynamicSystemPreparer extends BaseTargetPreparer { ZipFile zipFile = null; File systemImage = null; + File rawSystemImage = null; File systemImageGZ = null; try { zipFile = new ZipFile(systemImageZipFile); systemImage = ZipUtil2.extractFileFromZip(zipFile, "system.img"); - // The prequest here is the system.img must be an unsparsed image. - // Is there any way to detect the actual format and convert it accordingly. + if (SparseImageUtil.isSparse(systemImage)) { + rawSystemImage = FileUtil.createTempFile("system", ".raw"); + SparseImageUtil.unsparse(systemImage, rawSystemImage); + } else { + // system.img is already non-sparse + rawSystemImage = systemImage; + } systemImageGZ = FileUtil.createTempFile("system", ".raw.gz"); - long rawSize = systemImage.length(); - ZipUtil.gzipFile(systemImage, systemImageGZ); + long rawSize = rawSystemImage.length(); + ZipUtil.gzipFile(rawSystemImage, systemImageGZ); CLog.i("Pushing %s to %s", systemImageGZ.getAbsolutePath(), DEST_PATH); if (!device.pushFile(systemImageGZ, DEST_PATH)) { throw new TargetSetupError( @@ -95,17 +108,26 @@ public class DynamicSystemPreparer extends BaseTargetPreparer { + "--el KEY_SYSTEM_SIZE " + rawSize + " " - + "--el KEY_USERDATA_SIZE 8589934592 " - + "--ez KEY_ENABLE_WHEN_COMPLETED true"; + + "--el KEY_USERDATA_SIZE " + + mUserDataSizeInGb * 1024 * 1024 * 1024 + + " --ez KEY_ENABLE_WHEN_COMPLETED true"; device.executeShellCommand(command); // Check if device shows as unavailable (as expected after the activity finished). - device.waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000); - device.waitForDeviceOnline(); - // the waitForDeviceOnline may block and we need to correct the 'i' - // which is used to measure timeout accordingly - if (!isDSURunning(device)) { + if (!device.waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000)) { throw new TargetSetupError( - "Timeout to boot into DSU", device.getDeviceDescriptor()); + "Timed out waiting for DSU installation to complete and reboot", + device.getDeviceDescriptor()); + } + try { + // waitForDeviceOnline() throws DeviceNotAvailableException if device does not + // become online within timeout. + device.waitForDeviceOnline(); + } catch (DeviceNotAvailableException e) { + throw new TargetSetupError( + "Timed out booting into DSU", e, device.getDeviceDescriptor()); + } + if (!isDSURunning(device)) { + throw new TargetSetupError("Failed to boot into DSU", device.getDeviceDescriptor()); } CommandResult result = device.executeShellV2Command("gsi_tool enable"); if (CommandStatus.SUCCESS.equals(result.getStatus())) { @@ -120,6 +142,7 @@ public class DynamicSystemPreparer extends BaseTargetPreparer { "fail to install the DynamicSystemUpdate", e, device.getDeviceDescriptor()); } finally { FileUtil.deleteFile(systemImage); + FileUtil.deleteFile(rawSystemImage); FileUtil.deleteFile(systemImageGZ); ZipUtil2.closeZip(zipFile); } diff --git a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java index f3de9b444..0e61a42be 100644 --- a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java +++ b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java @@ -29,6 +29,9 @@ import com.android.tradefed.device.ITestDevice; import com.android.tradefed.invoker.IInvocationContext; import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.result.error.DeviceErrorIdentifier; +import com.android.tradefed.result.error.ErrorIdentifier; +import com.android.tradefed.result.error.InfraErrorIdentifier; import com.android.tradefed.testtype.IAbi; import com.android.tradefed.testtype.IAbiReceiver; import com.android.tradefed.testtype.IInvocationContextReceiver; @@ -137,9 +140,10 @@ public class PushFilePreparer extends BaseTargetPreparer * Helper method to only throw if mAbortOnFailure is enabled. Callers should behave as if this * method may return. */ - private void fail(String message, DeviceDescriptor descriptor) throws TargetSetupError { + private void fail(String message, DeviceDescriptor descriptor, ErrorIdentifier identifier) + throws TargetSetupError { if (shouldAbortOnFailure()) { - throw new TargetSetupError(message, descriptor); + throw new TargetSetupError(message, descriptor, identifier); } else { // Log the error and return Log.w(LOG_TAG, message); @@ -153,7 +157,10 @@ public class PushFilePreparer extends BaseTargetPreparer for (String pushspec : mPushSpecs) { String[] pair = pushspec.split("->"); if (pair.length != 2) { - fail(String.format("Invalid pushspec: '%s'", Arrays.asList(pair)), descriptor); + fail( + String.format("Invalid pushspec: '%s'", Arrays.asList(pair)), + descriptor, + InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR); continue; } remoteToLocalMapping.put(pair[1], new File(pair[0])); @@ -311,6 +318,22 @@ public class PushFilePreparer extends BaseTargetPreparer // approach to do individual download from remote artifact. // Try to stage the files from remote zip files. src = buildInfo.stageRemoteFile(fileName, testDir); + if (src != null) { + try { + // Search again with filtering on ABI + File srcWithAbi = FileUtil.findFile(fileName, mAbi, testDir); + if (srcWithAbi != null + && !srcWithAbi + .getAbsolutePath() + .startsWith(src.getAbsolutePath())) { + // When multiple matches are found, return the one with matching + // ABI unless src is its parent directory. + return srcWithAbi; + } + } catch (IOException e) { + CLog.w("Failed to find test files with matching ABI from directory."); + } + } } } return src; @@ -388,7 +411,8 @@ public class PushFilePreparer extends BaseTargetPreparer if (src == null || !src.exists()) { fail( String.format("Local source file '%s' does not exist", localPath), - device.getDeviceDescriptor()); + device.getDeviceDescriptor(), + InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND); return; } if (src.isDirectory()) { @@ -402,7 +426,8 @@ public class PushFilePreparer extends BaseTargetPreparer String.format( "Attempting to push dir '%s' to an existing device file '%s'", src.getAbsolutePath(), remotePath), - device.getDeviceDescriptor()); + device.getDeviceDescriptor(), + DeviceErrorIdentifier.FAIL_PUSH_FILE); } Set<String> filter = new HashSet<>(); if (mAbi != null) { @@ -415,7 +440,8 @@ public class PushFilePreparer extends BaseTargetPreparer fail( String.format( "Failed to push local '%s' to remote '%s'", localPath, remotePath), - device.getDeviceDescriptor()); + device.getDeviceDescriptor(), + DeviceErrorIdentifier.FAIL_PUSH_FILE); return; } else { if (deleteContentOnly) { @@ -428,7 +454,8 @@ public class PushFilePreparer extends BaseTargetPreparer fail( String.format( "Failed to push local '%s' to remote '%s'", localPath, remotePath), - device.getDeviceDescriptor()); + device.getDeviceDescriptor(), + DeviceErrorIdentifier.FAIL_PUSH_FILE); return; } else { mFilesPushed.add(remotePath); diff --git a/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java b/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java index 7034330ef..90928da3f 100644 --- a/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java +++ b/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java @@ -26,6 +26,7 @@ import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; import com.android.tradefed.util.FileUtil; import com.android.tradefed.util.IRunUtil; +import com.android.tradefed.util.PythonVirtualenvHelper; import com.android.tradefed.util.RunUtil; import java.io.File; @@ -40,8 +41,7 @@ import java.util.List; @OptionClass(alias = "python-venv") public class PythonVirtualenvPreparer extends BaseTargetPreparer { - private static final String PIP = "pip"; - private static final String PATH = "PATH"; + private static final String PIP = "pip3"; protected static final String PYTHONPATH = "PYTHONPATH"; private static final int BASE_TIMEOUT = 1000 * 60; @@ -70,6 +70,7 @@ public class PythonVirtualenvPreparer extends BaseTargetPreparer { protected void installDeps(IBuildInfo buildInfo, ITestDevice device) throws TargetSetupError { boolean hasDependencies = false; + mPip = getPipPath(); if (mRequirementsFile != null) { CommandResult c = mRunUtil.runTimedCmd(BASE_TIMEOUT * 5, mPip, "install", "-r", mRequirementsFile.getAbsolutePath()); @@ -90,6 +91,9 @@ public class PythonVirtualenvPreparer extends BaseTargetPreparer { CLog.e("Installing %s failed", dep); throw new TargetSetupError("Failed to install dependencies with pip", device.getDeviceDescriptor()); + } else { + CLog.d("Successfullly installed %s.", dep); + CLog.d("Stdout: %s", c.getStdout()); } hasDependencies = true; } @@ -99,9 +103,12 @@ public class PythonVirtualenvPreparer extends BaseTargetPreparer { } else { // make the install directory of new packages available to other classes that // receive the build - buildInfo.setFile(PYTHONPATH, new File(mVenvDir, - "local/lib/python2.7/site-packages"), + // TODO(b/166688272): Get install location from pip rather than hard code it. + buildInfo.setFile( + PYTHONPATH, + new File(mVenvDir, "local/lib/python3.8/site-packages"), buildInfo.getBuildId()); + buildInfo.setFile("VIRTUAL_ENV", mVenvDir, buildInfo.getBuildId()); } } @@ -109,13 +116,26 @@ public class PythonVirtualenvPreparer extends BaseTargetPreparer { throws TargetSetupError { if (mVenvDir != null) { CLog.i("Using existing virtualenv based at %s", mVenvDir.getAbsolutePath()); - activate(); + PythonVirtualenvHelper.activate(mRunUtil, mVenvDir); return; } + checkVirtualenvVersion(device); try { mVenvDir = FileUtil.createNamedTempDir(buildInfo.getTestTag() + "-virtualenv"); - mRunUtil.runTimedCmd(BASE_TIMEOUT, "virtualenv", mVenvDir.getAbsolutePath()); - activate(); + CommandResult c = + mRunUtil.runTimedCmd(BASE_TIMEOUT, "virtualenv", mVenvDir.getAbsolutePath()); + if (c.getStatus() != CommandStatus.SUCCESS) { + CLog.e("Creating virtual environment at %s failed.", mVenvDir.getAbsoluteFile()); + CLog.e( + "Status: %s\nStdout: %s\nStderr: %s", + c.getStatus(), c.getStdout(), c.getStderr()); + throw new TargetSetupError( + String.format( + "Failed to create virtual environment. Error:\n%s", c.getStderr()), + device.getDeviceDescriptor()); + } + CLog.i("Created a virtualenv based at %s", mVenvDir.getAbsolutePath()); + PythonVirtualenvHelper.activate(mRunUtil, mVenvDir); } catch (IOException e) { CLog.e("Failed to create temp directory for virtualenv"); throw new TargetSetupError("Error creating virtualenv", e, @@ -131,13 +151,35 @@ public class PythonVirtualenvPreparer extends BaseTargetPreparer { mRequirementsFile = f; } - private void activate() { - File binDir = new File(mVenvDir, "bin"); - mRunUtil.setWorkingDir(binDir); - String path = System.getenv(PATH); - mRunUtil.setEnvVariable(PATH, binDir + ":" + path); - File pipFile = new File(binDir, PIP); + private String getPipPath() { + if (mVenvDir == null || !mVenvDir.exists()) { + return null; + } + String virtualenvPath = mVenvDir.getAbsolutePath(); + File pipFile = new File(PythonVirtualenvHelper.getPythonBinDir(virtualenvPath), PIP); pipFile.setExecutable(true); - mPip = pipFile.getAbsolutePath(); + return pipFile.getAbsolutePath(); + } + + /** Check if the virtualenv on the host is too old. */ + private void checkVirtualenvVersion(ITestDevice device) throws TargetSetupError { + CommandResult result = mRunUtil.runTimedCmd(BASE_TIMEOUT, "virtualenv", "--version"); + if (!CommandStatus.SUCCESS.equals(result.getStatus())) { + throw new TargetSetupError( + "Failed to run `virtualenv --version`. Reason:\n" + result.getStderr(), + device.getDeviceDescriptor()); + } + String stdout = result.getStdout(); // should start with 'virtualenv <version> from' + if (stdout.contains("command not found")) { + throw new TargetSetupError( + "virtualenv is not installed.", device.getDeviceDescriptor()); + } + String version = stdout.split(" ")[1]; + int majorVersion = Integer.parseInt(version.split("\\.")[0]); + if (majorVersion < 20) { + throw new TargetSetupError( + "virtualenv is too old. Required: >=20.0.1, yours: " + version, + device.getDeviceDescriptor()); + } } }
\ No newline at end of file diff --git a/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java index e3c485ce2..fd4c69474 100644 --- a/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java +++ b/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java @@ -16,9 +16,11 @@ package com.android.tradefed.targetprep; +import com.android.tradefed.config.GlobalConfiguration; import com.android.tradefed.config.Option; import com.android.tradefed.config.OptionClass; import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.IDeviceManager; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.log.ITestLogger; @@ -86,6 +88,11 @@ public class RunHostCommandTargetPreparer extends BaseTargetPreparer @Option(name = "host-cmd-timeout", description = "Timeout for each command specified.") private Duration mTimeout = Duration.ofMinutes(1L); + @Option( + name = "use-flashing-permit", + description = "Acquire a flashing permit before running commands.") + private boolean mUseFlashingPermit = false; + private List<Process> mBgProcesses = new ArrayList<>(); private List<BgCommandLog> mBgCommandLogs = new ArrayList<>(); private ITestLogger mLogger; @@ -147,7 +154,16 @@ public class RunHostCommandTargetPreparer extends BaseTargetPreparer } ITestDevice device = testInfo.getDevice(); replaceSerialNumber(mSetUpCommands, device); - runCommandList(mSetUpCommands, device); + try { + if (mUseFlashingPermit) { + getDeviceManager().takeFlashingPermit(); + } + runCommandList(mSetUpCommands, device); + } finally { + if (mUseFlashingPermit) { + getDeviceManager().returnFlashingPermit(); + } + } try { mBgCommandLogs = createBgCommandLogs(); @@ -164,9 +180,16 @@ public class RunHostCommandTargetPreparer extends BaseTargetPreparer ITestDevice device = testInfo.getDevice(); replaceSerialNumber(mTearDownCommands, device); try { + if (mUseFlashingPermit) { + getDeviceManager().takeFlashingPermit(); + } runCommandList(mTearDownCommands, device); } catch (TargetSetupError tse) { CLog.e(tse); + } finally { + if (mUseFlashingPermit) { + getDeviceManager().returnFlashingPermit(); + } } // Terminate background commands after test finished @@ -273,6 +296,12 @@ public class RunHostCommandTargetPreparer extends BaseTargetPreparer return mRunUtil; } + /** @return {@link IDeviceManager} instance used for flashing permits */ + @VisibleForTesting + IDeviceManager getDeviceManager() { + return GlobalConfiguration.getDeviceManagerInstance(); + } + /** * Create a BgCommandLog object that is based on a temporary file for each background command * diff --git a/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java index def925213..6dd2b561b 100644 --- a/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java +++ b/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java @@ -56,6 +56,11 @@ public class RunHostScriptTargetPreparer extends BaseTargetPreparer { @Option(name = "script-timeout", description = "Script execution timeout.") private Duration mTimeout = Duration.ofMinutes(1L); + @Option( + name = "use-flashing-permit", + description = "Acquire a flashing permit before executing the script.") + private boolean mUseFlashingPermit = false; + private IRunUtil mRunUtil; @Override @@ -82,28 +87,15 @@ public class RunHostScriptTargetPreparer extends BaseTargetPreparer { getRunUtil().setEnvVariable("ANDROID_SERIAL", device.getSerialNumber()); setPathVariable(testInfo); - // Execute script and handle result - CommandResult result = - getRunUtil().runTimedCmd(mTimeout.toMillis(), scriptFile.getAbsolutePath()); - switch (result.getStatus()) { - case SUCCESS: - CLog.i("Script executed successfully, stdout = [%s].", result.getStdout()); - break; - case FAILED: - throw new TargetSetupError( - String.format( - "Script execution failed, stdout = [%s], stderr = [%s].", - result.getStdout(), result.getStderr()), - device.getDeviceDescriptor()); - case TIMED_OUT: - throw new TargetSetupError( - "Script execution timed out.", device.getDeviceDescriptor()); - case EXCEPTION: - throw new TargetSetupError( - String.format( - "Exception during script execution, stdout = [%s], stderr = [%s].", - result.getStdout(), result.getStderr()), - device.getDeviceDescriptor()); + try { + if (mUseFlashingPermit) { + getDeviceManager().takeFlashingPermit(); + } + executeScript(scriptFile, device); + } finally { + if (mUseFlashingPermit) { + getDeviceManager().returnFlashingPermit(); + } } } @@ -116,7 +108,7 @@ public class RunHostScriptTargetPreparer extends BaseTargetPreparer { return mRunUtil; } - /** @return {@link IDeviceManager} instance used to fetch the configured adb/fastboot paths */ + /** @return {@link IDeviceManager} instance used for adb/fastboot paths and flashing permits */ @VisibleForTesting IDeviceManager getDeviceManager() { return GlobalConfiguration.getDeviceManagerInstance(); @@ -175,4 +167,35 @@ public class RunHostScriptTargetPreparer extends BaseTargetPreparer { getRunUtil().setEnvVariable("PATH", path); } } + + /** + * Execute script and handle result. + * + * @param scriptFile script file to execute + * @param device device being prepared + */ + private void executeScript(File scriptFile, ITestDevice device) throws TargetSetupError { + CommandResult result = + getRunUtil().runTimedCmd(mTimeout.toMillis(), scriptFile.getAbsolutePath()); + switch (result.getStatus()) { + case SUCCESS: + CLog.i("Script executed successfully, stdout = [%s].", result.getStdout()); + break; + case FAILED: + throw new TargetSetupError( + String.format( + "Script execution failed, stdout = [%s], stderr = [%s].", + result.getStdout(), result.getStderr()), + device.getDeviceDescriptor()); + case TIMED_OUT: + throw new TargetSetupError( + "Script execution timed out.", device.getDeviceDescriptor()); + case EXCEPTION: + throw new TargetSetupError( + String.format( + "Exception during script execution, stdout = [%s], stderr = [%s].", + result.getStdout(), result.getStderr()), + device.getDeviceDescriptor()); + } + } } diff --git a/test_framework/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java new file mode 100644 index 000000000..7f2b28d86 --- /dev/null +++ b/test_framework/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.targetprep; + +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.device.UserInfo; +import com.android.tradefed.invoker.TestInformation; + +import com.google.common.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * An {@link ITargetPreparer} that creates a secondary user in setup, and marks that tests should be + * run in that user. + * + * <p>In teardown, the secondary user is removed. + */ +@OptionClass(alias = "run-on-secondary-user") +public class RunOnSecondaryUserTargetPreparer extends BaseTargetPreparer { + + @VisibleForTesting static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER"; + + @VisibleForTesting static final String TEST_PACKAGE_NAME_OPTION = "test-package-name"; + + @Option( + name = TEST_PACKAGE_NAME_OPTION, + description = + "the name of a package to be installed on the secondary user. " + + "This must already be installed on the device.", + importance = Option.Importance.IF_UNSET) + private List<String> mTestPackages = new ArrayList<>(); + + @Override + public void setUp(TestInformation testInfo) + throws TargetSetupError, DeviceNotAvailableException { + int secondaryUserId = getSecondaryUserId(testInfo.getDevice()); + + if (secondaryUserId != -1) { + // There is already a secondary user - so we don't want to remove it + setDisableTearDown(true); + } else { + secondaryUserId = createSecondaryUser(testInfo.getDevice()); + } + + for (String pkg : mTestPackages) { + testInfo.getDevice() + .executeShellCommand( + "pm install-existing --user " + secondaryUserId + " " + pkg); + } + + testInfo.properties().put(RUN_TESTS_AS_USER_KEY, Integer.toString(secondaryUserId)); + } + + /** Get the id of a secondary user currently on the device. -1 if there is none */ + private static int getSecondaryUserId(ITestDevice device) throws DeviceNotAvailableException { + for (Map.Entry<Integer, UserInfo> userInfo : device.getUserInfos().entrySet()) { + if (userInfo.getValue().isSecondary()) { + return userInfo.getKey(); + } + } + return -1; + } + + /** Creates a secondary user and returns the new user ID. */ + private static int createSecondaryUser(ITestDevice device) throws DeviceNotAvailableException { + final String createUserOutput = device.executeShellCommand("pm create-user secondary"); + final int userId = Integer.parseInt(createUserOutput.split(" id ")[1].trim()); + device.executeShellCommand("am start-user -w " + userId); + return userId; + } + + @Override + public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException { + int userId = Integer.parseInt(testInfo.properties().get(RUN_TESTS_AS_USER_KEY)); + + testInfo.getDevice().removeUser(userId); + } +} diff --git a/test_framework/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java new file mode 100644 index 000000000..274a09604 --- /dev/null +++ b/test_framework/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.targetprep; + +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.device.UserInfo; +import com.android.tradefed.invoker.TestInformation; + +import com.google.common.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * An {@link ITargetPreparer} that creates a work profile in setup, and marks that tests should be + * run in that user. + * + * <p>In teardown, the work profile is removed. + */ +@OptionClass(alias = "run-on-work-profile") +public class RunOnWorkProfileTargetPreparer extends BaseTargetPreparer { + + @VisibleForTesting static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER"; + + @VisibleForTesting static final String TEST_PACKAGE_NAME_OPTION = "test-package-name"; + + @Option( + name = TEST_PACKAGE_NAME_OPTION, + description = + "the name of a package to be installed on the work profile. " + + "This must already be installed on the device.", + importance = Option.Importance.IF_UNSET) + private List<String> mTestPackages = new ArrayList<>(); + + @Override + public void setUp(TestInformation testInfo) + throws TargetSetupError, DeviceNotAvailableException { + int workProfileId = getWorkProfileId(testInfo.getDevice()); + + if (workProfileId != -1) { + // There is already a work profile - so we don't want to remove it + setDisableTearDown(true); + } else { + workProfileId = createWorkProfile(testInfo.getDevice()); + } + + for (String pkg : mTestPackages) { + testInfo.getDevice() + .executeShellCommand("pm install-existing --user " + workProfileId + " " + pkg); + } + + testInfo.properties().put(RUN_TESTS_AS_USER_KEY, Integer.toString(workProfileId)); + } + + /** Get the id of a work profile currently on the device. -1 if there is none */ + private static int getWorkProfileId(ITestDevice device) throws DeviceNotAvailableException { + for (Map.Entry<Integer, UserInfo> userInfo : device.getUserInfos().entrySet()) { + if (userInfo.getValue().isManagedProfile()) { + return userInfo.getKey(); + } + } + return -1; + } + + /** Creates a work profile and returns the new user ID. */ + private static int createWorkProfile(ITestDevice device) throws DeviceNotAvailableException { + int parentProfile = device.getCurrentUser(); + final String createUserOutput = + device.executeShellCommand( + "pm create-user --profileOf " + parentProfile + " --managed work"); + final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim()); + device.executeShellCommand("am start-user -w " + profileId); + return profileId; + } + + @Override + public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException { + int workProfileId = Integer.parseInt(testInfo.properties().get(RUN_TESTS_AS_USER_KEY)); + + testInfo.getDevice().removeUser(workProfileId); + } +} diff --git a/test_framework/com/android/tradefed/targetprep/WifiPreparer.java b/test_framework/com/android/tradefed/targetprep/WifiPreparer.java index 1bfc63567..b1f14e09d 100644 --- a/test_framework/com/android/tradefed/targetprep/WifiPreparer.java +++ b/test_framework/com/android/tradefed/targetprep/WifiPreparer.java @@ -64,10 +64,14 @@ public class WifiPreparer extends BaseTargetPreparer { if (mVerifyOnly) { if (!device.isWifiEnabled()) { throw new TargetSetupError( - "The device does not have wifi enabled.", device.getDeviceDescriptor()); + "The device does not have wifi enabled.", + device.getDeviceDescriptor(), + InfraErrorIdentifier.NO_WIFI); } else if (!device.checkConnectivity()) { throw new TargetSetupError( - "The device has no wifi connection.", device.getDeviceDescriptor()); + "The device has no wifi connection.", + device.getDeviceDescriptor(), + InfraErrorIdentifier.NO_WIFI); } return; } diff --git a/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java b/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java index d436e13ea..cec9d30e2 100644 --- a/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java +++ b/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java @@ -95,10 +95,6 @@ public class MixImageZipPreparer extends BaseMultiTargetPreparer { ) private Set<String> mSystemFileNames = new TreeSet<>(); - @Deprecated - @Option(name = "dummy-file-name", description = "use stub-file-name instead.") - private Set<String> mDummyFileNames = new TreeSet<>(); - @Option( name = "stub-file-name", description = @@ -221,7 +217,6 @@ public class MixImageZipPreparer extends BaseMultiTargetPreparer { systemFiles = replaceExistingEntries(systemFiles, files); filesNotInDeviceBuild.putAll(systemFiles); - mStubFileNames.addAll(mDummyFileNames); // Generate specified stub files and replace those in device build. Map<String, InputStreamFactory> stubFiles = createStubInputStreamFactories(mStubFileNames); diff --git a/test_framework/com/android/tradefed/testtype/ArtGTest.java b/test_framework/com/android/tradefed/testtype/ArtGTest.java new file mode 100644 index 000000000..cf6a33396 --- /dev/null +++ b/test_framework/com/android/tradefed/testtype/ArtGTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.testtype; + +import com.android.tradefed.targetprep.ArtChrootPreparer; + +public class ArtGTest extends GTest { + @Override + protected String getGTestCmdLineWrapper(String fullPath, String flags) { + String chroot = ArtChrootPreparer.CHROOT_PATH; + if (fullPath.startsWith(chroot)) { + fullPath = fullPath.substring(chroot.length()); + } + return String.format("chroot %s %s %s", chroot, fullPath, flags); + } +} diff --git a/test_framework/com/android/tradefed/testtype/ArtRunTest.java b/test_framework/com/android/tradefed/testtype/ArtRunTest.java index a98210452..dbeaae5bf 100644 --- a/test_framework/com/android/tradefed/testtype/ArtRunTest.java +++ b/test_framework/com/android/tradefed/testtype/ArtRunTest.java @@ -17,35 +17,51 @@ package com.android.tradefed.testtype; import com.android.ddmlib.CollectingOutputReceiver; - import com.android.tradefed.config.Option; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.invoker.ExecutionFiles.FilesKey; +import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; +import com.android.tradefed.result.FileInputStreamSource; import com.android.tradefed.result.ITestInvocationListener; +import com.android.tradefed.result.LogDataType; import com.android.tradefed.result.TestDescription; import com.android.tradefed.util.AbiUtils; import com.android.tradefed.util.ArrayUtil; import com.android.tradefed.util.FileUtil; +import difflib.DiffUtils; +import difflib.Patch; + +import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; /** A test runner to run ART run-tests. */ -public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver { +public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver, ITestFilterReceiver { private static final String RUNTEST_TAG = "ArtRunTest"; private static final String DALVIKVM_CMD = "dalvikvm|#BITNESS#| -classpath |#CLASSPATH#| |#MAINCLASS#|"; + public static final String CHECKER_EXECUTABLE = "art/tools/checker/checker.py"; @Option( name = "test-timeout", @@ -63,6 +79,8 @@ public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver { private ITestDevice mDevice = null; private IAbi mAbi = null; + private Set<String> mIncludeFilters = new LinkedHashSet<>(); + private Set<String> mExcludeFilters = new LinkedHashSet<>(); /** {@inheritDoc} */ @Override @@ -89,6 +107,54 @@ public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver { /** {@inheritDoc} */ @Override + public void addIncludeFilter(String filter) { + mIncludeFilters.add(filter); + } + + /** {@inheritDoc} */ + @Override + public void addAllIncludeFilters(Set<String> filters) { + mIncludeFilters.addAll(filters); + } + + /** {@inheritDoc} */ + @Override + public void addExcludeFilter(String filter) { + mExcludeFilters.add(filter); + } + + /** {@inheritDoc} */ + @Override + public void addAllExcludeFilters(Set<String> filters) { + mExcludeFilters.addAll(filters); + } + + /** {@inheritDoc} */ + @Override + public Set<String> getIncludeFilters() { + return mIncludeFilters; + } + + /** {@inheritDoc} */ + @Override + public Set<String> getExcludeFilters() { + return mExcludeFilters; + } + + /** {@inheritDoc} */ + @Override + public void clearIncludeFilters() { + mIncludeFilters.clear(); + } + + /** {@inheritDoc} */ + @Override + public void clearExcludeFilters() { + mExcludeFilters.clear(); + } + + /** {@inheritDoc} */ + @Override public void run(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException { if (mDevice == null) { @@ -111,24 +177,29 @@ public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver { * Run a single ART run-test (on device). * * @param listener {@link ITestInvocationListener} listener for test - * @throws DeviceNotAvailableException + * @throws DeviceNotAvailableException If there was a problem communicating with + * the test device. */ void runArtTest(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException { + String abi = mAbi.getName(); + String runName = String.format("%s_%s", RUNTEST_TAG, abi); + TestDescription testId = new TestDescription(runName, mRunTestName); + if (shouldSkipCurrentTest(testId)) { + return; + } + CLog.i("Running ArtRunTest %s on %s", mRunTestName, mDevice.getSerialNumber()); String cmd = DALVIKVM_CMD; - String abi = mAbi.getName(); cmd = cmd.replace("|#BITNESS#|", AbiUtils.getBitness(abi)); cmd = cmd.replace("|#CLASSPATH#|", ArrayUtil.join(File.pathSeparator, mClasspath)); // TODO: Turn this into an an option of the `ArtRunTest` class? cmd = cmd.replace("|#MAINCLASS#|", "Main"); CLog.d("About to run run-test command: %s", cmd); - String runName = String.format("%s_%s", RUNTEST_TAG, abi); // Note: We only run one test at the moment. int testCount = 1; - TestDescription testId = new TestDescription(runName, mRunTestName); listener.testRunStarted(runName, testCount); listener.testStarted(testId); @@ -143,57 +214,171 @@ public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver { // Check the output producted by the test. if (output != null) { try { - File expectedFile = getDependencyFileFromRunTestDir(testInfo, "expected.txt"); + String expectedFileName = String.format("%s-expected.txt", mRunTestName); + File expectedFile = + testInfo.getDependencyFile(expectedFileName, /* targetFirst */ true); CLog.i("Found expected output for run-test %s: %s", mRunTestName, expectedFile); String expected = FileUtil.readStringFromFile(expectedFile); + + // TODO: The "check" step should be configurable, as is the case in current ART + // `run-test` scripts). if (!output.equals(expected)) { - String error = String.format("'%s' instead of '%s'", output, expected); - // TODO: Implement better reporting, e.g. using a diff output. - // Also, the "check" step should be configurable, as this is the case in - // current ART run-test scripts). - CLog.i("%s FAILED: %s", mRunTestName, error); - listener.testFailed(testId, error); + // Compute the difference between the expected and actual outputs. + List<String> expectedLines = Arrays.asList(expected.split("\\r?\\n")); + List<String> outputLines = Arrays.asList(output.split("\\r?\\n")); + Patch<String> diff = DiffUtils.diff(expectedLines, outputLines); + List<String> unifiedDiff = + DiffUtils.generateUnifiedDiff( + "expected.txt", "stdout", expectedLines, diff, 3); + // Produce a unified diff output for the error message. + StringBuilder errorMessage = + new StringBuilder( + "The test's standard output does not match the expected " + + "output:\n"); + for (String delta : unifiedDiff) { + errorMessage.append(delta).append('\n'); + } + CLog.i("%s FAILED: %s", mRunTestName, errorMessage.toString()); + listener.testFailed(testId, errorMessage.toString()); + return; } } catch (IOException ioe) { CLog.e( "I/O error while accessing expected output file for test %s: %s", mRunTestName, ioe); listener.testFailed(testId, "I/O error while accessing expected output file."); + return; } } else { listener.testFailed(testId, "No output received to compare to."); + return; + } + + if (mRunTestName.contains("-checker-")) { + // not particularly reliable way of constructing a temporary dir + String cfgPathDir = + String.format("/data/local/tmp/%s", mRunTestName.replaceAll("/", "-")); + mDevice.executeShellCommand(String.format("mkdir -p \"%s\"", cfgPathDir)); + + String cfgPath = cfgPathDir + "/graph.cfg"; + mDevice.executeShellCommand( + String.format( + "dex2oat --dex-file=%s --oat-file=/dev/null --dump-cfg=%s -j1", + mClasspath.get(0), cfgPath)); + + File runTestDir; + try { + runTestDir = getRunTestDir(testInfo); + } catch (FileNotFoundException e) { + listener.testFailed(testId, "I/O error while accessing test dir."); + return; + } + + File localCfgPath = new File(runTestDir, "graph.cfg"); + if (localCfgPath.isFile()) { + localCfgPath.delete(); + } + + mDevice.pullFile(cfgPath, localCfgPath); + + File tempJar = new File(runTestDir, "temp.jar"); + mDevice.pullFile(mClasspath.get(0), tempJar); + + try (ZipFile archive = new ZipFile(tempJar)) { + File srcFile = new File(runTestDir, "src"); + if (srcFile.exists()) { + Files.walk(srcFile.toPath()) + .map(Path::toFile) + .sorted(Comparator.reverseOrder()) + .forEach(File::delete); + } + + List<? extends ZipEntry> entries = archive.stream() + .sorted(Comparator.comparing(ZipEntry::getName)) + .collect(Collectors.toList()); + + for (ZipEntry entry : entries) { + if (entry.getName().startsWith("src")) { + Path entryDest = runTestDir.toPath().resolve(entry.getName()); + if (entry.isDirectory()) { + Files.createDirectory(entryDest); + } else { + Files.copy(archive.getInputStream(entry), entryDest); + } + } + } + } catch (IOException e) { + listener.testFailed(testId, "Error unpacking test jar"); + CLog.e("Jar unpacking failed with exception %s", e); + CLog.e(e); + return; + } + + String checkerArch = AbiUtils.getArchForAbi(abi).toUpperCase(); + + ProcessBuilder processBuilder = + new ProcessBuilder( + CHECKER_EXECUTABLE, + "-q", + "--arch=" + checkerArch, + localCfgPath.getAbsolutePath(), + runTestDir.getAbsolutePath()); + + try { + Process process = processBuilder.start(); + if (process.waitFor() != 0) { + String checkerOutput = new BufferedReader( + new InputStreamReader(process.getErrorStream())).lines().collect( + Collectors.joining("\n")); + listener.testFailed(testId, "Checker failed\n" + checkerOutput); + listener.testLog("graph.cfg", LogDataType.CFG, + new FileInputStreamSource(localCfgPath)); + } + } catch (IOException | InterruptedException e) { + listener.testFailed(testId, "I/O error while starting Checker process"); + } } } finally { - HashMap<String, Metric> emptyTestMetrics = new HashMap(); + HashMap<String, Metric> emptyTestMetrics = new HashMap<>(); listener.testEnded(testId, emptyTestMetrics); - HashMap<String, Metric> emptyTestRunMetrics = new HashMap(); + HashMap<String, Metric> emptyTestRunMetrics = new HashMap<>(); // TODO: Pass an actual value as `elapsedTimeMillis` argument. listener.testRunEnded(/* elapsedTimeMillis*/ 0, emptyTestRunMetrics); } } + /** + * Check if current test should be skipped. + * + * @param description The test in progress. + * @return true if the test should be skipped. + */ + private boolean shouldSkipCurrentTest(TestDescription description) { + // Force to skip any test not listed in include filters, or listed in exclude filters. + // exclude filters have highest priority. + String testName = description.getTestName(); + String descString = description.toString(); + if (mExcludeFilters.contains(testName) || mExcludeFilters.contains(descString)) { + return true; + } + if (!mIncludeFilters.isEmpty()) { + return !mIncludeFilters.contains(testName) && !mIncludeFilters.contains(descString); + } + return false; + } + /** Create an output receiver for the test command executed on the device. */ protected CollectingOutputReceiver createTestOutputReceiver() { return new CollectingOutputReceiver(); } - /** Search for a dependency/artifact file in the run-test's directory. */ - protected File getDependencyFileFromRunTestDir(TestInformation testInfo, String fileName) - throws FileNotFoundException { + private File getRunTestDir(TestInformation testInfo) throws FileNotFoundException { File testsDir = testInfo.executionFiles().get(FilesKey.TARGET_TESTS_DIRECTORY); if (testsDir == null || !testsDir.exists()) { throw new FileNotFoundException( String.format( "Could not find target tests directory for test %s.", mRunTestName)); } - File runTestDir = new File(testsDir, mRunTestName); - File file = FileUtil.findFile(runTestDir, fileName); - if (file == null) { - throw new FileNotFoundException( - String.format( - "Could not find an artifact file associated with %s in directory %s.", - fileName, runTestDir)); - } - return file; + return new File(testsDir, mRunTestName); } } diff --git a/test_framework/com/android/tradefed/testtype/GTest.java b/test_framework/com/android/tradefed/testtype/GTest.java index 70beba964..cd971f443 100644 --- a/test_framework/com/android/tradefed/testtype/GTest.java +++ b/test_framework/com/android/tradefed/testtype/GTest.java @@ -158,6 +158,10 @@ public class GTest extends GTestBase implements IDeviceTest { return testPath.toString(); } + public void setNativeTestDevicePath(String path) { + mNativeTestDevicePath = path; + } + /** * Executes all native tests in a folder as well as in all subfolders recursively. * diff --git a/test_framework/com/android/tradefed/testtype/GTestBase.java b/test_framework/com/android/tradefed/testtype/GTestBase.java index 316d2f957..a7eaea79b 100644 --- a/test_framework/com/android/tradefed/testtype/GTestBase.java +++ b/test_framework/com/android/tradefed/testtype/GTestBase.java @@ -547,6 +547,14 @@ public abstract class GTestBase } /** + * Helper which allows derived classes to wrap the gtest command under some other tool (chroot, + * strace, gdb, and similar). + */ + protected String getGTestCmdLineWrapper(String fullPath, String flags) { + return String.format("%s %s", fullPath, flags); + } + + /** * Helper method to build the gtest command to run. * * @param fullPath absolute file system path to gtest binary on device @@ -568,7 +576,7 @@ public abstract class GTestBase gTestCmdLine.append(String.format("su %s ", mRunTestAs)); } - gTestCmdLine.append(String.format("%s %s", fullPath, flags)); + gTestCmdLine.append(getGTestCmdLineWrapper(fullPath, flags)); return gTestCmdLine.toString(); } diff --git a/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java index 373844a51..837ba81f7 100644 --- a/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java +++ b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java @@ -26,6 +26,7 @@ import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.util.proto.TfMetricProtoUtil; +import com.android.tradefed.util.StringEscapeUtils; import com.google.common.annotations.VisibleForTesting; @@ -406,7 +407,7 @@ public class GoogleBenchmarkTest implements IDeviceTest, IRemoteTest, ITestFilte if (iterator.hasNext()) { filterFlag.append(String.format(" %s=%s", GBENCHMARK_FILTER_OPTION, iterator.next())); while (iterator.hasNext()) { - filterFlag.append(String.format("\\|%s", iterator.next())); + filterFlag.append(String.format("|%s", iterator.next())); } } return filterFlag.toString(); @@ -421,7 +422,7 @@ public class GoogleBenchmarkTest implements IDeviceTest, IRemoteTest, ITestFilte // Format benchmark as "^benchmark$" to avoid unintended regex partial matching. filterFlag.append(String.format(" %s=^%s$", GBENCHMARK_FILTER_OPTION, iterator.next())); while (iterator.hasNext()) { - filterFlag.append(String.format("\\|^%s$", iterator.next())); + filterFlag.append(String.format("|^%s$", iterator.next())); } } return filterFlag.toString(); @@ -441,14 +442,14 @@ public class GoogleBenchmarkTest implements IDeviceTest, IRemoteTest, ITestFilte final String cmd, final IShellOutputReceiver outputReceiver) throws DeviceNotAvailableException { + String shellCmd = StringEscapeUtils.escapeShell(cmd); // Ensure that command is not too long for adb - if (cmd.length() < ADB_CMD_CHAR_LIMIT) { + if (shellCmd.length() < ADB_CMD_CHAR_LIMIT) { if (outputReceiver == null) { - return testDevice.executeShellCommand(cmd); + return testDevice.executeShellCommand(shellCmd); } - testDevice.executeShellCommand( - cmd, + shellCmd, outputReceiver, mMaxRunTime /* maxTimeToShellOutputResponse */, TimeUnit.MILLISECONDS, @@ -457,7 +458,7 @@ public class GoogleBenchmarkTest implements IDeviceTest, IRemoteTest, ITestFilte } // Wrap adb shell command in script if command is too long for direct execution - return executeCommandByScript(testDevice, cmd, outputReceiver); + return executeCommandByScript(testDevice, shellCmd, outputReceiver); } /** Runs a command from a temporary script. */ diff --git a/test_framework/com/android/tradefed/testtype/HostGTest.java b/test_framework/com/android/tradefed/testtype/HostGTest.java index 43d599e1e..3da63e465 100644 --- a/test_framework/com/android/tradefed/testtype/HostGTest.java +++ b/test_framework/com/android/tradefed/testtype/HostGTest.java @@ -41,21 +41,10 @@ import java.util.List; /** A Test that runs a native test package. */ @OptionClass(alias = "hostgtest") -public class HostGTest extends GTestBase implements IAbiReceiver, IBuildReceiver { +public class HostGTest extends GTestBase implements IBuildReceiver { private static final long DEFAULT_HOST_COMMAND_TIMEOUT_MS = 2 * 60 * 1000; private IBuildInfo mBuildInfo = null; - private IAbi mAbi = null; - - @Override - public void setAbi(IAbi abi) { - this.mAbi = abi; - } - - @Override - public IAbi getAbi() { - return this.mAbi; - } @Override public void setBuild(IBuildInfo buildInfo) { @@ -216,7 +205,7 @@ public class HostGTest extends GTestBase implements IAbiReceiver, IBuildReceiver String moduleName = getTestModule(); File gTestFile = null; try { - gTestFile = FileUtil.findFile(moduleName, mAbi, scanDirs.toArray(new File[] {})); + gTestFile = FileUtil.findFile(moduleName, getAbi(), scanDirs.toArray(new File[] {})); } catch (IOException e) { throw new RuntimeException(e); } @@ -226,7 +215,8 @@ public class HostGTest extends GTestBase implements IAbiReceiver, IBuildReceiver // search for it with a potential suffix (which is allowed). try { File byBaseName = - FileUtil.findFile(moduleName + ".*", mAbi, scanDirs.toArray(new File[] {})); + FileUtil.findFile( + moduleName + ".*", getAbi(), scanDirs.toArray(new File[] {})); if (byBaseName != null && byBaseName.isFile()) { gTestFile = byBaseName; } diff --git a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java index bba5fa39b..b1f7f1ad2 100644 --- a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java +++ b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java @@ -47,6 +47,7 @@ import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; import com.android.tradefed.result.BugreportCollector; import com.android.tradefed.result.CollectingTestListener; import com.android.tradefed.result.ITestInvocationListener; +import com.android.tradefed.result.ITestLifeCycleReceiver; import com.android.tradefed.result.TestDescription; import com.android.tradefed.result.TestResult; import com.android.tradefed.result.TestRunResult; @@ -100,6 +101,8 @@ public class InstrumentationTest /** default timeout for tests collection */ static final long TEST_COLLECTION_TIMEOUT_MS = 2 * 60 * 1000; + static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER"; + /** test run name for merging coverage measurements */ static final String MERGE_COVERAGE_MEASUREMENTS_TEST_NAME = "mergeCoverageMeasurements"; @@ -886,7 +889,8 @@ public class InstrumentationTest "Tests to run should not be set explicitly when --collect-tests-only is set."); // Use the actual listener to collect the tests, and print a error if this fails - Collection<TestDescription> collectedTests = collectTestsToRun(mRunner, listener); + Collection<TestDescription> collectedTests = + collectTestsToRun(testInfo, mRunner, listener); if (collectedTests == null) { CLog.e("Failed to collect tests for %s", mPackageName); } else { @@ -899,7 +903,7 @@ public class InstrumentationTest Collection<TestDescription> testsToRun = mTestsToRun; if (testsToRun == null) { // Don't notify the listener since it's not a real run. - testsToRun = collectTestsToRun(mRunner, null); + testsToRun = collectTestsToRun(testInfo, mRunner, null); } // Only set the debug flag after collecting tests. @@ -962,7 +966,7 @@ public class InstrumentationTest if (testsToRun == null) { // Failed to collect the tests or collection is off. Just try to run them all. - mDevice.runInstrumentationTests(mRunner, listener); + runInstrumentationTests(testInfo, mRunner, listener); } else if (!testsToRun.isEmpty()) { runWithRerun(testInfo, listener, testsToRun); } else { @@ -977,6 +981,20 @@ public class InstrumentationTest } } + private boolean runInstrumentationTests( + TestInformation testInfo, + IRemoteAndroidTestRunner runner, + ITestLifeCycleReceiver... receivers) + throws DeviceNotAvailableException { + if (testInfo != null && testInfo.properties().containsKey(RUN_TESTS_AS_USER_KEY)) { + return mDevice.runInstrumentationTestsAsUser( + runner, + Integer.parseInt(testInfo.properties().get(RUN_TESTS_AS_USER_KEY)), + receivers); + } + return mDevice.runInstrumentationTests(runner, receivers); + } + /** * Returns a listener that will collect bugreports, or the original {@code listener} if this * feature is disabled. @@ -1071,7 +1089,7 @@ public class InstrumentationTest getDevice().getProcessByName("system_server")); } instrumentationListener.setReportUnexecutedTests(mReportUnexecuted); - mDevice.runInstrumentationTests(mRunner, instrumentationListener); + runInstrumentationTests(testInfo, mRunner, instrumentationListener); TestRunResult testRun = testTracker.getCurrentRunResults(); if (testRun.isRunFailure() || !testRun.getCompletedTests().containsAll(expectedTests)) { // Don't re-run any completed tests, unless this is a coverage run. @@ -1172,7 +1190,9 @@ public class InstrumentationTest * @throws DeviceNotAvailableException */ private Collection<TestDescription> collectTestsToRun( - final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener) + final TestInformation testInfo, + final IRemoteAndroidTestRunner runner, + final ITestInvocationListener listener) throws DeviceNotAvailableException { if (isRerunMode()) { Log.d(LOG_TAG, String.format("Collecting test info for %s on device %s", @@ -1182,7 +1202,7 @@ public class InstrumentationTest runner.setDebug(false); // try to collect tests multiple times, in case device is temporarily not available // on first attempt - Collection<TestDescription> tests = collectTestsAndRetry(runner, listener); + Collection<TestDescription> tests = collectTestsAndRetry(testInfo, runner, listener); // done with "logOnly" mode, restore proper test timeout before real test execution addTimeoutsToRunner(runner); runner.setTestCollection(false); @@ -1202,7 +1222,9 @@ public class InstrumentationTest */ @VisibleForTesting Collection<TestDescription> collectTestsAndRetry( - final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener) + final TestInformation testInfo, + final IRemoteAndroidTestRunner runner, + final ITestInvocationListener listener) throws DeviceNotAvailableException { boolean communicationFailure = false; for (int i=0; i < COLLECT_TESTS_ATTEMPTS; i++) { @@ -1211,9 +1233,9 @@ public class InstrumentationTest // We allow to override the ddmlib default timeout for collection of tests. runner.setMaxTimeToOutputResponse(mCollectTestTimeout, TimeUnit.MILLISECONDS); if (listener == null) { - instrResult = mDevice.runInstrumentationTests(runner, collector); + instrResult = runInstrumentationTests(testInfo, runner, collector); } else { - instrResult = mDevice.runInstrumentationTests(runner, collector, listener); + instrResult = runInstrumentationTests(testInfo, runner, collector, listener); } TestRunResult runResults = collector.getCurrentRunResults(); if (!instrResult || !runResults.isRunComplete()) { diff --git a/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java b/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java index 67cd44cff..950f8dfe0 100644 --- a/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java +++ b/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java @@ -24,10 +24,13 @@ import com.android.tradefed.config.OptionClass; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.invoker.IInvocationContext; import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; +import com.android.tradefed.result.FailureDescription; import com.android.tradefed.result.FileInputStreamSource; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.InputStreamSource; import com.android.tradefed.result.LogDataType; +import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; import com.android.tradefed.targetprep.adb.AdbStopServerPreparer; import com.android.tradefed.testtype.IBuildReceiver; import com.android.tradefed.testtype.IDeviceTest; @@ -37,6 +40,7 @@ import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; import com.android.tradefed.util.FileUtil; import com.android.tradefed.util.IRunUtil; +import com.android.tradefed.util.PythonVirtualenvHelper; import com.android.tradefed.util.RunUtil; import com.android.tradefed.util.StreamUtil; @@ -48,6 +52,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -136,7 +141,11 @@ public class MoblyBinaryHostTest @Override public final void run(ITestInvocationListener listener) { - List<File> parFilesList = findParFiles(); + List<File> parFilesList = findParFiles(listener); + File venvDir = mBuildInfo.getFile("VIRTUAL_ENV"); + if (venvDir != null) { + PythonVirtualenvHelper.activate(getRunUtil(), venvDir); + } for (File parFile : parFilesList) { // TODO(b/159365341): add a failure reporting for nonexistent binary. if (!parFile.exists()) { @@ -153,9 +162,13 @@ public class MoblyBinaryHostTest reportLogs(getLogDir(), listener); } } + if (venvDir != null + && venvDir.getAbsolutePath().startsWith(System.getProperty("java.io.tmpdir"))) { + FileUtil.recursiveDelete(venvDir); + } } - private List<File> findParFiles() { + private List<File> findParFiles(ITestInvocationListener listener) { File testsDir = null; if (mBuildInfo instanceof IDeviceBuildInfo) { testsDir = ((IDeviceBuildInfo) mBuildInfo).getTestsDir(); @@ -169,6 +182,8 @@ public class MoblyBinaryHostTest res = FileUtil.findFile(testsDir, binaryName); } if (res == null) { + reportFailure( + listener, binaryName, "Couldn't find Mobly test binary " + binaryName); throw new RuntimeException( String.format("Couldn't find a par file %s", binaryName)); } @@ -251,31 +266,43 @@ public class MoblyBinaryHostTest InputStream inputStream = null; try { inputStream = new FileInputStream(yamlSummaryFile); - processYamlTestResults(inputStream, parser); + processYamlTestResults(inputStream, parser, listener, runName); } catch (FileNotFoundException ex) { - // TODO(b/159367088): report a test failure. - CLog.e("Fail processing test results: ", ex); + reportFailure( + listener, + runName, + "Fail processing test results, result file not found.\n" + ex); } finally { StreamUtil.close(inputStream); } } + /** + * Parses Mobly test results and does result reporting. + * + * @param inputStream An InputStream object reading in Mobly test result file. + * @param parser An MoblyYamlResultParser object that processes Mobly test results. + * @param listener An ITestInvocationListener instance that does various reporting. + * @param runName str, the name of the Mobly test binary run. + */ @VisibleForTesting - protected void processYamlTestResults(InputStream inputStream, MoblyYamlResultParser parser) { + protected void processYamlTestResults( + InputStream inputStream, + MoblyYamlResultParser parser, + ITestInvocationListener listener, + String runName) { try { parser.parse(inputStream); } catch (MoblyYamlResultHandlerFactory.InvalidResultTypeException | IllegalAccessException | InstantiationException ex) { - // TODO(b/159367088): report a test failure. - CLog.e("Failed to parse result file: %s", ex); + reportFailure(listener, runName, "Failed to parse the result file.\n" + ex); } } private void updateConfigFile() { InputStream inputStream = null; FileWriter fileWriter = null; - // TODO(b/159369745): clean up the tmp files created. File localConfigFile = new File(getLogDir(), "local_config.yaml"); try { inputStream = new FileInputStream(mConfigFile); @@ -334,6 +361,15 @@ public class MoblyBinaryHostTest return mLogDir; } + private void reportFailure( + ITestInvocationListener listener, String runName, String errorMessage) { + listener.testRunStarted(runName, 0); + FailureDescription description = + FailureDescription.create(errorMessage, FailureStatus.TEST_FAILURE); + listener.testRunFailed(description); + listener.testRunEnded(0L, new HashMap<String, Metric>()); + } + @VisibleForTesting String getLogDirAbsolutePath() { return getLogDir().getAbsolutePath(); @@ -348,6 +384,8 @@ public class MoblyBinaryHostTest protected String[] buildCommandLineArray(String filePath) { List<String> commandLine = new ArrayList<>(); commandLine.add(filePath); + // TODO(b/166468397): some test binaries are actually a wrapper of Mobly runner and need -- + // to separate Python options. commandLine.add("--"); if (getConfigPath() != null) { commandLine.add("--config=" + getConfigPath()); diff --git a/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultHandlerFactory.java b/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultHandlerFactory.java index 4d4a7aeda..e0938518b 100644 --- a/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultHandlerFactory.java +++ b/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultHandlerFactory.java @@ -45,6 +45,8 @@ public class MoblyYamlResultHandlerFactory { return resultHandler; } + // MoblyYamlResultHandlerFactory won't be serialized so suppress serial warning. + @SuppressWarnings("serial") public class InvalidResultTypeException extends Exception { public InvalidResultTypeException(String errorMsg) { super(errorMsg); @@ -59,9 +61,9 @@ public class MoblyYamlResultHandlerFactory { SUMMARY("Summary", MoblyYamlResultSummaryHandler.class); private String tag; - private Class handlerClass; + private Class<?> handlerClass; - Type(String tag, Class handlerClass) { + Type(String tag, Class<?> handlerClass) { this.tag = tag; this.handlerClass = handlerClass; } diff --git a/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultParser.java b/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultParser.java index d3a692cfd..e3dda0055 100644 --- a/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultParser.java +++ b/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultParser.java @@ -40,9 +40,9 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; public class MoblyYamlResultParser { private static final String TYPE = "Type"; private ImmutableList.Builder<ITestInvocationListener> mListenersBuilder = - new ImmutableList.Builder(); + new ImmutableList.Builder<>(); private final String mRunName; - private ImmutableList.Builder<ITestResult> mResultCacheBuilder = new ImmutableList.Builder(); + private ImmutableList.Builder<ITestResult> mResultCacheBuilder = new ImmutableList.Builder<>(); private int mTestCount; private long mRunStartTime; private long mRunEndTime; diff --git a/test_framework/com/android/tradefed/util/PythonVirtualenvHelper.java b/test_framework/com/android/tradefed/util/PythonVirtualenvHelper.java new file mode 100644 index 000000000..35c419020 --- /dev/null +++ b/test_framework/com/android/tradefed/util/PythonVirtualenvHelper.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.util; + +import com.android.tradefed.log.LogUtil.CLog; + +import java.io.File; +import java.util.stream.Stream; + +/** A helper class for activating Python 3 virtual environment. */ +public class PythonVirtualenvHelper { + + private static final String PATH = "PATH"; + private static final String PYTHONHOME = "PYTHONHOME"; + private static final String PYTHONPATH = "PYTHONPATH"; + public static final String VIRTUAL_ENV = "VIRTUAL_ENV"; + + /** + * Gets python bin directory path. + * + * <p>This method will check the directory existence. + * + * @return str, the path to the python bin directory in venv. + * @throws NullPointerException if arg virtualenvPath is null. + * @throws RuntimeException if /path/to/venv/bin does not exist. + */ + public static String getPythonBinDir(String virtualenvPath) { + if (virtualenvPath == null) { + throw new NullPointerException( + "Path to the Python virtual environment should not be null"); + } + File res = new File(virtualenvPath, "bin"); + if (!res.exists()) { + throw new RuntimeException("Invalid python virtualenv path " + res.getAbsolutePath()); + } + return res.getAbsolutePath(); + } + + /** + * Activate virtualenv for a RunUtil. + * + * @param runUtil an utility object for running virtualenv activation commands. + * @param virtualenvDir a File object representing the created virtualenv directory. + */ + public static void activate(IRunUtil runUtil, File virtualenvDir) { + activate(runUtil, virtualenvDir.getAbsolutePath()); + } + + /** + * Activate virtualenv for a RunUtil. + * + * <p>This method will check for python bin directory existence + * + * @param runUtil an utility object for running virtualenv activation commands. + * @param virtualenvPath the path to the created virtualenv directory. + */ + public static void activate(IRunUtil runUtil, String virtualenvPath) { + String pythonBinDir = getPythonBinDir(virtualenvPath); + String separater = ":"; + String pythonPath = + getPackageInstallLocation(runUtil, virtualenvPath) + + separater + + System.getenv(PYTHONPATH); + runUtil.setEnvVariable(PATH, pythonBinDir + separater + System.getenv().get(PATH)); + runUtil.setEnvVariable(VIRTUAL_ENV, virtualenvPath); + runUtil.setEnvVariable(PYTHONPATH, pythonPath); + runUtil.unsetEnvVariable(PYTHONHOME); + CLog.d("Activating virtual environment:"); + CLog.d("%s: %s", PATH, pythonBinDir + separater + System.getenv().get(PATH)); + CLog.d("%s: %s", VIRTUAL_ENV, virtualenvPath); + CLog.d("%s: %s", PYTHONPATH, pythonPath); + } + + /** + * Gets the absolute path to the pip3 binary in the given venv directory. + * + * @param virtualenvPath the path to the venv directory. + * @return a string representing the absolute path to the pip3 binary. + */ + private static String getPipPath(String virtualenvPath) { + File pipFile = new File(PythonVirtualenvHelper.getPythonBinDir(virtualenvPath), "pip3"); + return pipFile.getAbsolutePath(); + } + + /** + * Gets python package install location. + * + * <p>This method will call /path/to/venv/bin/pip3 show pip and parse out package location from + * stdout output. + * + * @param runUtil an utility object for running for running commands. + * @param virtualenvPath the path to the created virtualenv directory. + * @return a string representing the absolute path to the location where Python packages are + * installed. + */ + private static String getPackageInstallLocation(IRunUtil runUtil, String virtualenvPath) { + CommandResult result = + runUtil.runTimedCmd(60000, getPipPath(virtualenvPath), "show", "pip"); + if (result.getStatus() != CommandStatus.SUCCESS) { + throw new RuntimeException( + String.format( + "Fail to run command: %s show pip.\nStatus:%s\nStdout:%s\nStderr:%s", + getPipPath(virtualenvPath), + result.getStatus(), + result.getStdout(), + result.getStderr())); + } + String stdout = result.getStdout(); + String[] lines = stdout.split("\n"); + String locationLine = + Stream.of(lines).filter(x -> x.startsWith("Location")).findFirst().orElse(""); + return locationLine.split(" ")[1]; + } +} diff --git a/tests/Android.bp b/tests/Android.bp index 7d1334af0..caf95bbc3 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -12,6 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +java_library_host { + name: "tradefed-test-protos", + srcs: ["res/**/*.proto"], + libs: [ + "libprotobuf-java-full", + ], + proto: { + include_dirs: ["external/protobuf/src"], + type: "full", + } +} + tradefed_java_library_host { name: "tradefed-tests", defaults: ["tradefed_errorprone_defaults"], @@ -30,6 +42,7 @@ tradefed_java_library_host { "easymock", "objenesis", "mockito", + "tradefed-test-protos", ], libs: [ "tradefed", diff --git a/tests/res/proto/proto_util_test.proto b/tests/res/proto/proto_util_test.proto new file mode 100644 index 000000000..a59332660 --- /dev/null +++ b/tests/res/proto/proto_util_test.proto @@ -0,0 +1,38 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "com.android.tradefed.util.test"; +option java_outer_classname = "ProtoUtilTestProto"; + +// A proto for com.android.tradefed.util.ProtoUtilTest. +message TestMessage { + string string_field = 1; + int64 int_field = 2; + repeated string repeated_string_field = 3; + + message SubMessage { + int64 int_field = 1; + repeated string repeated_string_field = 2; + } + + SubMessage message_field = 4; + repeated SubMessage repeated_message_field = 5; + + oneof oneof_field { + string oneof_string_field = 6; + SubMessage oneof_message_field = 7; + } +} diff --git a/tests/res/testconfigs/yaml/not-target-preparer.tf_yaml b/tests/res/testconfigs/yaml/not-target-preparer.tf_yaml new file mode 100644 index 000000000..610b63795 --- /dev/null +++ b/tests/res/testconfigs/yaml/not-target-preparer.tf_yaml @@ -0,0 +1,16 @@ +description: "Human friendly description of the test" + +# pre_setup_action will run before the dependencies installation +pre_setup_action: + - action: + name: "com.android.tradefed.testtype.AndroidJUnitTest" + options: + - package: "android.package" + +dependencies: + +tests: + - test: + name: "com.android.tradefed.testtype.AndroidJUnitTest" + options: + - package: "android.package" diff --git a/tests/res/testconfigs/yaml/test-config.tf_yaml b/tests/res/testconfigs/yaml/test-config.tf_yaml index eb89137b3..2df8a06c9 100644 --- a/tests/res/testconfigs/yaml/test-config.tf_yaml +++ b/tests/res/testconfigs/yaml/test-config.tf_yaml @@ -1,11 +1,29 @@ description: "Human friendly description of the test" +# pre_setup_action will run before the dependencies installation +pre_setup_action: + - action: + name: "com.android.tradefed.targetprep.RunCommandTargetPreparer" + options: + - run-command: "dumpsys value" + - action: + name: "com.android.tradefed.targetprep.RunCommandTargetPreparer" + options: + - run-command: "another one" + dependencies: - apks: ["test.apk", "test2.apk"] - apks: ["test1.apk"] - files: ["file1.txt", "file2.txt"] - device_files: {"tobepushed.txt": "/sdcard", "tobepushed2.txt": "/sdcard/"} +# post_setup_action will run after the dependencies installation +post_setup_action: + - action: + name: "com.android.tradefed.targetprep.RunCommandTargetPreparer" + options: + - run-command: "dumpsys value2" + tests: - test: name: "com.android.tradefed.testtype.AndroidJUnitTest" diff --git a/tests/res/util/partial_zip.zip b/tests/res/util/partial_zip.zip Binary files differindex 1f50fef78..4fb046ea7 100644 --- a/tests/res/util/partial_zip.zip +++ b/tests/res/util/partial_zip.zip diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java index 897aacef3..74717a58a 100644 --- a/tests/src/com/android/tradefed/UnitTests.java +++ b/tests/src/com/android/tradefed/UnitTests.java @@ -89,6 +89,7 @@ import com.android.tradefed.device.cloud.GceRemoteCmdFormatterTest; import com.android.tradefed.device.cloud.GceSshTunnelMonitorTest; import com.android.tradefed.device.cloud.ManagedRemoteDeviceTest; import com.android.tradefed.device.cloud.NestedRemoteDeviceTest; +import com.android.tradefed.device.cloud.RemoteAndroidVirtualDeviceTest; import com.android.tradefed.device.cloud.RemoteFileUtilTest; import com.android.tradefed.device.contentprovider.ContentProviderHandlerTest; import com.android.tradefed.device.helper.TelephonyHelperTest; @@ -96,33 +97,21 @@ import com.android.tradefed.device.metric.AtraceCollectorTest; import com.android.tradefed.device.metric.AtraceRunMetricCollectorTest; import com.android.tradefed.device.metric.AutoLogCollectorTest; import com.android.tradefed.device.metric.BaseDeviceMetricCollectorTest; -import com.android.tradefed.device.metric.BuddyInfoMetricCollectorTest; -import com.android.tradefed.device.metric.BugreportzMetricCollectorTest; import com.android.tradefed.device.metric.BugreportzOnFailureCollectorTest; import com.android.tradefed.device.metric.DebugHostLogOnFailureCollectorTest; import com.android.tradefed.device.metric.DeviceMetricDataTest; -import com.android.tradefed.device.metric.DumpHeapCollectorTest; import com.android.tradefed.device.metric.FilePullerDeviceMetricCollectorTest; import com.android.tradefed.device.metric.FilePullerLogCollectorTest; -import com.android.tradefed.device.metric.GraphicsStatsMetricCollectorTest; import com.android.tradefed.device.metric.GcovCodeCoverageCollectorTest; import com.android.tradefed.device.metric.HostStatsdMetricCollectorTest; import com.android.tradefed.device.metric.IncidentReportCollectorTest; -import com.android.tradefed.device.metric.IonHeapInfoMetricCollectorTest; import com.android.tradefed.device.metric.JavaCodeCoverageCollectorTest; import com.android.tradefed.device.metric.LogcatOnFailureCollectorTest; import com.android.tradefed.device.metric.LogcatTimingMetricCollectorTest; -import com.android.tradefed.device.metric.MemInfoMetricCollectorTest; -import com.android.tradefed.device.metric.PagetypeInfoMetricCollectorTest; import com.android.tradefed.device.metric.PerfettoPullerMetricCollectorTest; -import com.android.tradefed.device.metric.ProcessMaxMemoryCollectorTest; import com.android.tradefed.device.metric.RebootReasonCollectorTest; import com.android.tradefed.device.metric.RuntimeRestartCollectorTest; -import com.android.tradefed.device.metric.ScheduleMultipleDeviceMetricCollectorTest; -import com.android.tradefed.device.metric.ScheduledDeviceMetricCollectorTest; import com.android.tradefed.device.metric.ScreenshotOnFailureCollectorTest; -import com.android.tradefed.device.metric.TemperatureCollectorTest; -import com.android.tradefed.device.metric.TraceMetricCollectorTest; import com.android.tradefed.device.recovery.BatteryUnavailableDeviceRecoveryTest; import com.android.tradefed.device.recovery.RunConfigDeviceRecoveryTest; import com.android.tradefed.device.recovery.UsbResetMultiDeviceRecoveryTest; @@ -157,6 +146,7 @@ import com.android.tradefed.log.HistoryLoggerTest; import com.android.tradefed.log.LogRegistryTest; import com.android.tradefed.log.SimpleFileLoggerTest; import com.android.tradefed.log.TerribleFailureEmailHandlerTest; +import com.android.tradefed.monitoring.LabResourceDeviceMonitorTest; import com.android.tradefed.postprocessor.AggregatePostProcessorTest; import com.android.tradefed.postprocessor.AveragePostProcessorTest; import com.android.tradefed.postprocessor.BasePostProcessorTest; @@ -245,6 +235,8 @@ import com.android.tradefed.targetprep.RebootTargetPreparerTest; import com.android.tradefed.targetprep.RestartSystemServerTargetPreparerTest; import com.android.tradefed.targetprep.RootTargetPreparerTest; import com.android.tradefed.targetprep.RunCommandTargetPreparerTest; +import com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparerTest; +import com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparerTest; import com.android.tradefed.targetprep.RunHostCommandTargetPreparerTest; import com.android.tradefed.targetprep.RunHostScriptTargetPreparerTest; import com.android.tradefed.targetprep.StopServicesSetupTest; @@ -260,6 +252,7 @@ import com.android.tradefed.targetprep.multi.MergeMultiBuildTargetPreparerTest; import com.android.tradefed.targetprep.multi.MixImageZipPreparerTest; import com.android.tradefed.targetprep.suite.SuiteApkInstallerTest; import com.android.tradefed.testtype.AndroidJUnitTestTest; +import com.android.tradefed.testtype.ArtGTestTest; import com.android.tradefed.testtype.ArtRunTestTest; import com.android.tradefed.testtype.ClangCodeCoverageListenerTest; import com.android.tradefed.testtype.DeviceBatteryLevelCheckerTest; @@ -365,7 +358,9 @@ import com.android.tradefed.util.MultiMapTest; import com.android.tradefed.util.NativeCodeCoverageFlusherTest; import com.android.tradefed.util.PairTest; import com.android.tradefed.util.PropertyChangerTest; +import com.android.tradefed.util.ProtoUtilTest; import com.android.tradefed.util.PsParserTest; +import com.android.tradefed.util.PythonVirtualenvHelperTest; import com.android.tradefed.util.QuotationAwareTokenizerTest; import com.android.tradefed.util.RegexTrieTest; import com.android.tradefed.util.RemoteZipTest; @@ -379,6 +374,7 @@ import com.android.tradefed.util.SimplePerfUtilTest; import com.android.tradefed.util.SimpleStatsTest; import com.android.tradefed.util.SizeLimitedOutputStreamTest; import com.android.tradefed.util.Sl4aBluetoothUtilTest; +import com.android.tradefed.util.SparseImageUtilTest; import com.android.tradefed.util.StreamUtilTest; import com.android.tradefed.util.StringEscapeUtilsTest; import com.android.tradefed.util.StringUtilTest; @@ -519,7 +515,7 @@ import org.junit.runners.Suite.SuiteClasses; GceSshTunnelMonitorTest.class, ManagedRemoteDeviceTest.class, NestedRemoteDeviceTest.class, - RemoteAndroidDeviceTest.class, + RemoteAndroidVirtualDeviceTest.class, RemoteFileUtilTest.class, // device.contentprovider @@ -533,33 +529,21 @@ import org.junit.runners.Suite.SuiteClasses; AtraceRunMetricCollectorTest.class, AutoLogCollectorTest.class, BaseDeviceMetricCollectorTest.class, - BuddyInfoMetricCollectorTest.class, - BugreportzMetricCollectorTest.class, BugreportzOnFailureCollectorTest.class, DebugHostLogOnFailureCollectorTest.class, DeviceMetricDataTest.class, - DumpHeapCollectorTest.class, FilePullerDeviceMetricCollectorTest.class, FilePullerLogCollectorTest.class, - GraphicsStatsMetricCollectorTest.class, GcovCodeCoverageCollectorTest.class, IncidentReportCollectorTest.class, - IonHeapInfoMetricCollectorTest.class, JavaCodeCoverageCollectorTest.class, LogcatOnFailureCollectorTest.class, LogcatTimingMetricCollectorTest.class, - MemInfoMetricCollectorTest.class, - PagetypeInfoMetricCollectorTest.class, PerfettoPullerMetricCollectorTest.class, - ProcessMaxMemoryCollectorTest.class, RebootReasonCollectorTest.class, RuntimeRestartCollectorTest.class, - ScheduledDeviceMetricCollectorTest.class, - ScheduleMultipleDeviceMetricCollectorTest.class, ScreenshotOnFailureCollectorTest.class, HostStatsdMetricCollectorTest.class, - TemperatureCollectorTest.class, - TraceMetricCollectorTest.class, // device.recovery BatteryUnavailableDeviceRecoveryTest.class, @@ -583,7 +567,6 @@ import org.junit.runners.Suite.SuiteClasses; InvocationContextTest.class, InvocationExecutionTest.class, RemoteInvocationExecutionTest.class, - SandboxedInvocationExecutionTest.class, ShardListenerTest.class, ShardMainResultForwarderTest.class, TestInvocationMultiTest.class, @@ -607,7 +590,6 @@ import org.junit.runners.Suite.SuiteClasses; // invoker.sandbox ParentSandboxInvocationExecutionTest.class, - SandboxedInvocationExecutionTest.class, // lite DryRunnerTest.class, @@ -651,7 +633,6 @@ import org.junit.runners.Suite.SuiteClasses; MultiFailureDescriptionTest.class, SnapshotInputStreamSourceTest.class, SubprocessResultsReporterTest.class, - TestDescriptionTest.class, TestFailureEmailResultReporterTest.class, PassingTestFileReporterTest.class, TestDescriptionTest.class, @@ -711,6 +692,8 @@ import org.junit.runners.Suite.SuiteClasses; RunCommandTargetPreparerTest.class, RunHostCommandTargetPreparerTest.class, RunHostScriptTargetPreparerTest.class, + RunOnSecondaryUserTargetPreparerTest.class, + RunOnWorkProfileTargetPreparerTest.class, StopServicesSetupTest.class, SystemUpdaterDeviceFlasherTest.class, TargetSetupErrorTest.class, @@ -754,6 +737,7 @@ import org.junit.runners.Suite.SuiteClasses; // testtype AndroidJUnitTestTest.class, + ArtGTestTest.class, ArtRunTestTest.class, ClangCodeCoverageListenerTest.class, CoverageMeasurementForwarderTest.class, @@ -879,7 +863,9 @@ import org.junit.runners.Suite.SuiteClasses; MergedZipEntryCollectionTest.class, NativeCodeCoverageFlusherTest.class, PairTest.class, + ProtoUtilTest.class, PsParserTest.class, + PythonVirtualenvHelperTest.class, QuotationAwareTokenizerTest.class, RegexTrieTest.class, RemoteZipTest.class, @@ -893,6 +879,7 @@ import org.junit.runners.Suite.SuiteClasses; SimpleStatsTest.class, SizeLimitedOutputStreamTest.class, Sl4aBluetoothUtilTest.class, + SparseImageUtilTest.class, StreamUtilTest.class, StringEscapeUtilsTest.class, StringUtilTest.class, @@ -937,6 +924,9 @@ import org.junit.runners.Suite.SuiteClasses; // util/testmapping TestInfoTest.class, TestMappingTest.class, + + // monitoring + LabResourceDeviceMonitorTest.class, }) public class UnitTests { // empty of purpose diff --git a/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java b/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java index 3bf18e049..86248bd8b 100644 --- a/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java +++ b/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java @@ -15,6 +15,7 @@ */ package com.android.tradefed.cluster; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -35,11 +36,15 @@ import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.TestDescription; import com.android.tradefed.util.FileUtil; +import org.hamcrest.CoreMatchers; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; @@ -52,8 +57,9 @@ import java.util.HashMap; public class ClusterCommandLauncherFuncTest { private static final String LEGACY_TRADEFED_JAR = "/testdata/tradefed-prebuilt-cts-8.0_r21.jar"; - private static final String LEGACY_TRADEFED_COMMAND = - "host --null-device --class com.android.tradefed.device.DeviceDiagTest"; + private static final String LEGACY_TRADEFED_COMMAND = "fake.xml --null-device --run testRun PF"; + private static final String LEGACY_TRADEFED_COMMAND_FOR_INVOCATION_FAILURE = + "fake.xml --null-device --fail-invocation-with-cause cause"; private File mRootDir; private IConfiguration mConfiguration; @@ -82,22 +88,51 @@ public class ClusterCommandLauncherFuncTest { FileUtil.recursiveDelete(mRootDir); } - // @Ignore @Test public void testRun_withLegacyTradefed() throws IOException, ConfigurationException, DeviceNotAvailableException { File tfJar = new File(mRootDir, "tradefed.jar"); FileUtil.writeToFile(getClass().getResourceAsStream(LEGACY_TRADEFED_JAR), tfJar); + FileUtil.writeToFile( + getClass().getResourceAsStream("/config/tf/fake.xml"), + new File(mRootDir, "fake.xml")); mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", mRootDir.getAbsolutePath()); mOptionSetter.setOptionValue("cluster:use-subprocess-reporting", "true"); mOptionSetter.setOptionValue("cluster:command-line", LEGACY_TRADEFED_COMMAND); mLauncher.run(mTestInformation, mListener); + InOrder inOrder = Mockito.inOrder(mListener); HashMap<String, MetricMeasurement.Metric> emptyMap = new HashMap<>(); - verify(mListener).testRunStarted(anyString(), anyInt(), anyInt(), anyLong()); - verify(mListener).testStarted(any(TestDescription.class), anyLong()); - verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap)); - verify(mListener).testRunEnded(anyLong(), eq(emptyMap)); + inOrder.verify(mListener).testRunStarted(eq("testRun"), anyInt(), anyInt(), anyLong()); + inOrder.verify(mListener).testStarted(any(TestDescription.class), anyLong()); + inOrder.verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap)); + inOrder.verify(mListener).testStarted(any(TestDescription.class), anyLong()); + inOrder.verify(mListener).testFailed(any(TestDescription.class), anyString()); + inOrder.verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap)); + inOrder.verify(mListener).testRunEnded(anyLong(), eq(emptyMap)); + } + + @Test + public void testRun_withLegacyTradefed_invocationFailed() + throws IOException, ConfigurationException, DeviceNotAvailableException { + File tfJar = new File(mRootDir, "tradefed.jar"); + FileUtil.writeToFile(getClass().getResourceAsStream(LEGACY_TRADEFED_JAR), tfJar); + FileUtil.writeToFile( + getClass().getResourceAsStream("/config/tf/fake.xml"), + new File(mRootDir, "fake.xml")); + mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", mRootDir.getAbsolutePath()); + mOptionSetter.setOptionValue("cluster:use-subprocess-reporting", "true"); + mOptionSetter.setOptionValue( + "cluster:command-line", LEGACY_TRADEFED_COMMAND_FOR_INVOCATION_FAILURE); + + try { + mLauncher.run(mTestInformation, mListener); + fail("SubprocessCommandException should be thrown"); + } catch (SubprocessCommandException e) { + Assert.assertThat(e.getCause().getMessage(), CoreMatchers.containsString("cause")); + } + + verify(mListener).invocationFailed(any(Throwable.class)); } } diff --git a/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java b/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java index d4befb97e..adfc487ba 100644 --- a/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java +++ b/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java @@ -521,6 +521,23 @@ public class ClusterCommandSchedulerTest { new String[] {CMD_LINE, "--serial", "deviceSerial"}, getExecCommandArgs()); } + /** + * If a unique device serial (one with a hostname prefix) is specified for a command task, + * convert it to a local device serial before appending it. + */ + @Test + public void testExecCommandWithVirtualDeviceSerial() { + List<ClusterCommand> cmds = new ArrayList<>(); + ClusterCommand cmd = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); + cmd.setTargetDeviceSerials( + ArrayUtil.list(ClusterHostUtil.getHostName() + ":emulator-5554")); + cmds.add(cmd); + mScheduler.execCommands(cmds); + assertEquals(CMD_LINE, cmds.get(0).getCommandLine()); + assertArrayEquals( + new String[] {CMD_LINE, "--serial", "emulator-5554"}, getExecCommandArgs()); + } + /** Multiple serials specified for a command task. */ @Test public void testExecCommandWithMultipleSerials() { @@ -828,6 +845,70 @@ public class ClusterCommandSchedulerTest { handler.invocationInitiated(context); } + @Test + public void testInvocationEventHandler_withSubprocessCommandException() { + ClusterCommand mockCommand = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE); + IInvocationContext context = new InvocationContext(); + ITestDevice mockTestDevice = EasyMock.createMock(ITestDevice.class); + EasyMock.expect(mockTestDevice.getSerialNumber()).andReturn(DEVICE_SERIAL); + EasyMock.expect(mockTestDevice.getIDevice()).andReturn(new StubDevice(DEVICE_SERIAL)); + context.addAllocatedDevice("", mockTestDevice); + IBuildInfo mockBuildInfo = EasyMock.createMock(IBuildInfo.class); + context.addDeviceBuildInfo("", mockBuildInfo); + ClusterCommandScheduler.InvocationEventHandler handler = + mScheduler.new InvocationEventHandler(mockCommand); + mMockClusterOptions.setCollectEarlyTestSummary(true); + + mMockEventUploader.postEvent( + checkClusterCommandEvent(ClusterCommandEvent.Type.InvocationInitiated)); + mMockEventUploader.flush(); + mMockEventUploader.postEvent( + checkClusterCommandEvent(ClusterCommandEvent.Type.InvocationStarted)); + mMockEventUploader.flush(); + mMockEventUploader.postEvent( + checkClusterCommandEvent(ClusterCommandEvent.Type.TestRunInProgress)); + EasyMock.expectLastCall().anyTimes(); + mMockEventUploader.postEvent( + checkClusterCommandEvent(ClusterCommandEvent.Type.InvocationEnded)); + mMockEventUploader.flush(); + Capture<ClusterCommandEvent> capture = new Capture<>(); + mMockEventUploader.postEvent(EasyMock.capture(capture)); + mMockEventUploader.flush(); + + EasyMock.replay(mMockEventUploader, mockBuildInfo, mockTestDevice); + handler.invocationInitiated(context); + List<TestSummary> summaries = new ArrayList<>(); + summaries.add( + new TestSummary(new TestSummary.TypedString("http://uri", TestSummary.Type.URI))); + handler.putEarlySummary(summaries); + handler.putSummary(summaries); + handler.invocationStarted(context); + handler.invocationFailed( + new SubprocessCommandException( + "error_message", new Throwable("subprocess_command_error_message"))); + handler.invocationEnded(100L); + context.addAllocatedDevice(DEVICE_SERIAL, mockTestDevice); + Map<ITestDevice, FreeDeviceState> releaseMap = new HashMap<>(); + releaseMap.put(mockTestDevice, FreeDeviceState.AVAILABLE); + handler.invocationComplete(context, releaseMap); + EasyMock.verify(mMockEventUploader, mockBuildInfo, mockTestDevice); + ClusterCommandEvent capturedEvent = capture.getValue(); + assertTrue(capturedEvent.getType().equals(ClusterCommandEvent.Type.InvocationCompleted)); + assertTrue( + ((String) capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_ERROR)) + .contains("SubprocessCommandException")); + assertEquals( + "subprocess_command_error_message", + capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_SUBPROCESS_COMMAND_ERROR)); + assertEquals( + "0", capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_FAILED_TEST_COUNT)); + assertEquals( + "0", capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_PASSED_TEST_COUNT)); + assertEquals( + "URI: http://uri\n", + capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_SUMMARY)); + } + /** * Test that when dry-run is used we validate the config and no ConfigurationException gets * thrown. @@ -1058,7 +1139,8 @@ public class ClusterCommandSchedulerTest { List<IDeviceConfiguration> deviceConfigs = config.getDeviceConfig(); assertEquals(cmd.getTargetDeviceSerials().size(), deviceConfigs.size()); for (int i = 0; i < cmd.getTargetDeviceSerials().size(); i++) { - String serial = cmd.getTargetDeviceSerials().get(i); + String serial = + ClusterHostUtil.getLocalDeviceSerial(cmd.getTargetDeviceSerials().get(i)); Collection<String> serials = deviceConfigs.get(i).getDeviceRequirements().getSerials(null); assertTrue(serials.size() == 1 && serials.contains(serial)); @@ -1165,6 +1247,44 @@ public class ClusterCommandSchedulerTest { } } + /** Tests an execution of a managed cluster command. */ + @Test + public void testExecManagedClusterCommand_virtualDeviceTest() throws Exception { + File workDir = null; + try { + ClusterCommand cmd = createMockManagedCommand(1); + cmd.setTargetDeviceSerials( + ArrayUtil.list(ClusterHostUtil.getHostName() + ":emulator-5554")); + workDir = new File(System.getProperty("java.io.tmpdir"), cmd.getAttemptId()); + TestEnvironment testEnvironment = createMockTestEnvironment(); + List<TestResource> testResources = createMockTestResources(); + TestContext testContext = new TestContext(); + mMockClusterClient = Mockito.spy(mMockClusterClient); + Mockito.doReturn(testEnvironment) + .when(mMockClusterClient) + .getTestEnvironment(REQUEST_ID); + Mockito.doReturn(testResources).when(mMockClusterClient).getTestResources(REQUEST_ID); + Mockito.doReturn(testContext) + .when(mMockClusterClient) + .getTestContext(REQUEST_ID, COMMAND_ID); + InvocationEventHandler invocationEventHandler = + mScheduler.new InvocationEventHandler(cmd); + + mScheduler.execManagedClusterCommand(cmd, invocationEventHandler); + + String[] args = getExecCommandArgs(); + assertTrue(args.length > 0); + IConfiguration config = + ConfigurationFactory.getInstance().createConfigurationFromArgs(args); + verifyConfig(config, cmd, testEnvironment, testResources, workDir); + } finally { + if (workDir != null) { + // Clean up work directory + FileUtil.recursiveDelete(workDir); + } + } + } + /** Tests an execution of a managed cluster command for multiple devices. */ @Test public void testExecManagedClusterCommand_multiDeviceTest() throws Exception { diff --git a/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java b/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java index fc98304c6..d0ba372b9 100644 --- a/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java +++ b/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java @@ -100,6 +100,24 @@ public class ClusterDeviceMonitorTest { Assert.assertEquals("cluster1", hostEvent.getClusterId()); Assert.assertEquals(Arrays.asList("cluster2", "cluster3"), hostEvent.getNextClusterIds()); Assert.assertEquals("lab1", hostEvent.getLabName()); + Assert.assertEquals("", hostEvent.getData().get("label")); + } + + @Test + public void testLabel() throws Exception { + mClusterOptionSetter.setOptionValue("cluster:label", "label1"); + mClusterOptionSetter.setOptionValue("cluster:label", "label2"); + Capture<ClusterHostEvent> capture = new Capture<>(); + mHostEventUploader.postEvent(EasyMock.capture(capture)); + mHostEventUploader.flush(); + EasyMock.replay(mHostEventUploader); + mEventDispatcher.dispatch(); + EasyMock.verify(mHostEventUploader); + ClusterHostEvent hostEvent = capture.getValue(); + Assert.assertEquals("cluster1", hostEvent.getClusterId()); + Assert.assertEquals(Arrays.asList("cluster2", "cluster3"), hostEvent.getNextClusterIds()); + Assert.assertEquals("lab1", hostEvent.getLabName()); + Assert.assertEquals("label1,label2", hostEvent.getData().get("label")); } void setOptions() throws Exception { diff --git a/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java b/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java index e2c10bdfb..a7ff29c68 100644 --- a/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java +++ b/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java @@ -25,6 +25,8 @@ import java.security.InvalidParameterException; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.easymock.EasyMock; import org.junit.Assert; @@ -37,6 +39,7 @@ import org.junit.runners.JUnit4; public class ClusterHostUtilTest { private static final String DEVICE_SERIAL = "serial"; + private static final String EMULATOR_SERIAL = "emulator-5554"; @Test public void testIsIpPort() { @@ -287,4 +290,71 @@ public class ClusterHostUtilTest { "simOperator"); Assert.assertEquals("product", ClusterHostUtil.getRunTarget(device, format, null)); } + + @Test + public void testGetRunTarget_withStubDevice() { + final String hostname = ClusterHostUtil.getHostName(); + // with a stub device. + DeviceDescriptor device = + new DeviceDescriptor( + DEVICE_SERIAL, + true, + DeviceAllocationState.Available, + "product", + "productVariant", + "sdkVersion", + "buildId", + "batteryLevel"); + Assert.assertEquals( + hostname + ":" + DEVICE_SERIAL, + ClusterHostUtil.getRunTarget(device, "{SERIAL}", null)); + } + + @Test + public void testGetRunTarget_withEmulator() { + final String hostname = ClusterHostUtil.getHostName(); + // with a stub device. + DeviceDescriptor device = + new DeviceDescriptor( + EMULATOR_SERIAL, + false, + DeviceAllocationState.Available, + "product", + "productVariant", + "sdkVersion", + "buildId", + "batteryLevel"); + Assert.assertEquals( + hostname + ":" + EMULATOR_SERIAL, + ClusterHostUtil.getRunTarget(device, "{SERIAL}", null)); + } + + @Test + public void testGetRunTarget_withEmptyDeviceSerial() { + final String hostname = ClusterHostUtil.getHostName(); + // with a stub device. + DeviceDescriptor device = + new DeviceDescriptor( + "", + false, + DeviceAllocationState.Available, + "product", + "productVariant", + "sdkVersion", + "buildId", + "batteryLevel"); + Assert.assertEquals( + hostname + ":" + ClusterHostUtil.NULL_DEVICE_SERIAL_PLACEHOLDER, + ClusterHostUtil.getRunTarget(device, "{SERIAL}", null)); + } + + @Test + public void testGetHostIpAddress() { + final String hostIp = ClusterHostUtil.getHostIpAddress(); + Assert.assertNotEquals(hostIp, "127.0.0.1"); + Pattern pattern = + Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}" + "|UNKNOWN"); + Matcher matcher = pattern.matcher(hostIp); + Assert.assertTrue("host ip format not match: " + hostIp, matcher.matches()); + } } diff --git a/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java b/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java index f83ba2b03..67622f3b8 100644 --- a/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java +++ b/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java @@ -76,6 +76,23 @@ public class SubprocessConfigBuilderTest { verifyWrapperXml(doc, reporterPort); } + @Test + public void testCreateWrapperConfig_forCommandWithSlashes() throws Exception { + String oriConfigName = "util/timewaster"; + String reporterPort = "1024"; + mConfigBuilder + .setClasspath(mClasspath) + .setWorkingDir(mWorkDir) + .setOriginalConfig(oriConfigName) + .setPort(reporterPort); + File config = mConfigBuilder.build(); + assertNotNull(config); + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(config); + verifyWrapperXml(doc, reporterPort); + } + private void verifyWrapperXml(Document doc, String reporterPort) { NodeList reporters = doc.getElementsByTagName("result_reporter"); assertTrue(0 < reporters.getLength()); diff --git a/tests/src/com/android/tradefed/command/CommandSchedulerTest.java b/tests/src/com/android/tradefed/command/CommandSchedulerTest.java index 793ee11a4..7373e33bc 100644 --- a/tests/src/com/android/tradefed/command/CommandSchedulerTest.java +++ b/tests/src/com/android/tradefed/command/CommandSchedulerTest.java @@ -365,12 +365,14 @@ public class CommandSchedulerTest { mScheduler = new TestableCommandScheduler() { @Override - Map<String, ITestDevice> allocateDevices( + DeviceAllocationResult allocateDevices( IConfiguration config, IDeviceManager manager) { + DeviceAllocationResult results = new DeviceAllocationResult(); Map<String, ITestDevice> allocated = new HashMap<>(); ((MockDeviceManager) manager).addDevice(mockDevice); allocated.put("device", ((MockDeviceManager) manager).allocateDevice()); - return allocated; + results.addAllocatedDevices(allocated); + return results; } }; replayMocks(mockDevice, mockListener); @@ -1094,8 +1096,10 @@ public class CommandSchedulerTest { mMockConfiguration.validateOptions(); replayMocks(); mScheduler.start(); - Map<String, ITestDevice> devices = mScheduler.allocateDevices( - mMockConfiguration, mMockManager); + DeviceAllocationResult results = + mScheduler.allocateDevices(mMockConfiguration, mMockManager); + assertTrue(results.wasAllocationSuccessful()); + Map<String, ITestDevice> devices = results.getAllocatedDevices(); assertEquals(1, devices.size()); mScheduler.shutdown(); } @@ -1120,8 +1124,10 @@ public class CommandSchedulerTest { mMockConfiguration.setDeviceConfigList(EasyMock.anyObject()); replayMocks(); mScheduler.start(); - Map<String, ITestDevice> devices = + DeviceAllocationResult results = mScheduler.allocateDevices(mMockConfiguration, mMockManager); + assertTrue(results.wasAllocationSuccessful()); + Map<String, ITestDevice> devices = results.getAllocatedDevices(); // With replicated setup, all devices get allocated. assertEquals(3, devices.size()); mScheduler.shutdown(); @@ -1147,8 +1153,10 @@ public class CommandSchedulerTest { mMockConfiguration.validateOptions(); replayMocks(); mScheduler.start(); - Map<String, ITestDevice> devices = mScheduler.allocateDevices( - mMockConfiguration, mMockManager); + DeviceAllocationResult results = + mScheduler.allocateDevices(mMockConfiguration, mMockManager); + assertTrue(results.wasAllocationSuccessful()); + Map<String, ITestDevice> devices = results.getAllocatedDevices(); assertEquals(2, devices.size()); assertEquals(0, mMockManager.getQueueOfAvailableDeviceSize()); mScheduler.shutdown(); @@ -1166,8 +1174,10 @@ public class CommandSchedulerTest { mMockConfiguration.validateOptions(); replayMocks(); mScheduler.start(); - Map<String, ITestDevice> devices = mScheduler.allocateDevices( - mMockConfiguration, mMockManager); + DeviceAllocationResult results = + mScheduler.allocateDevices(mMockConfiguration, mMockManager); + assertFalse(results.wasAllocationSuccessful()); + Map<String, ITestDevice> devices = results.getAllocatedDevices(); assertEquals(0, devices.size()); assertEquals(2, mMockManager.getQueueOfAvailableDeviceSize()); mScheduler.shutdown(); @@ -1279,12 +1289,14 @@ public class CommandSchedulerTest { mScheduler = new TestableCommandScheduler() { @Override - Map<String, ITestDevice> allocateDevices( + DeviceAllocationResult allocateDevices( IConfiguration config, IDeviceManager manager) { + DeviceAllocationResult results = new DeviceAllocationResult(); Map<String, ITestDevice> allocated = new HashMap<>(); ((MockDeviceManager) manager).addDevice(mockDevice); allocated.put("device", ((MockDeviceManager) manager).allocateDevice()); - return allocated; + results.addAllocatedDevices(allocated); + return results; } }; diff --git a/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java b/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java index 87e60b755..c13780f04 100644 --- a/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java +++ b/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java @@ -1819,9 +1819,13 @@ public class ConfigurationFactoryTest extends TestCase { IBuildProvider provider = config.getBuildProvider(); assertTrue(provider instanceof IDeviceBuildProvider); IBuildInfo info = ((IDeviceBuildProvider) provider).getBuild(null); - assertEquals("5", info.getBuildId()); - assertEquals("test", info.getBuildFlavor()); - assertEquals("main", info.getBuildBranch()); + try { + assertEquals("5", info.getBuildId()); + assertEquals("test", info.getBuildFlavor()); + assertEquals("main", info.getBuildBranch()); + } finally { + info.cleanUp(); + } } private static String getClassName(String name) { diff --git a/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java b/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java index 69dd56d53..97730b4fe 100644 --- a/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java +++ b/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java @@ -604,7 +604,7 @@ public class DynamicRemoteFileResolverTest { IRemoteFileResolver actual = loader.load(NullFileResolver.PROTOCOL, ImmutableMap.of()); - assertThat(actual).isSameAs(expected); + assertThat(actual).isSameInstanceAs(expected); } @Test @@ -616,7 +616,7 @@ public class DynamicRemoteFileResolverTest { IRemoteFileResolver resolver1 = loader1.load(NullFileResolver.PROTOCOL, ImmutableMap.of()); IRemoteFileResolver resolver2 = loader2.load(NullFileResolver.PROTOCOL, ImmutableMap.of()); - assertThat(resolver1).isNotSameAs(resolver2); + assertThat(resolver1).isNotSameInstanceAs(resolver2); } @Test @@ -968,15 +968,9 @@ public class DynamicRemoteFileResolverTest { public static final class DuplicateNullFileResolver extends NullFileResolver {} private static final Correspondence<File, File> FILE_PATH_EQUIVALENCE = - new Correspondence<File, File>() { - @Override - public boolean compare(File actual, File expected) { - return expected.getAbsolutePath().equals(actual.getAbsolutePath()); - } - - @Override - public String toString() { - return "is equivalent to"; - } - }; + Correspondence.from( + (File actual, File expected) -> { + return expected.getAbsolutePath().equals(actual.getAbsolutePath()); + }, + "is equivalent to"); } diff --git a/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java b/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java index 6a1c08187..478444379 100644 --- a/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java +++ b/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java @@ -19,15 +19,18 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.android.tradefed.build.DependenciesResolver; import com.android.tradefed.build.StubBuildProvider; import com.android.tradefed.config.ConfigurationDef; +import com.android.tradefed.config.ConfigurationException; import com.android.tradefed.config.IConfiguration; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.suite.SuiteResultReporter; import com.android.tradefed.targetprep.ITargetPreparer; import com.android.tradefed.targetprep.PushFilePreparer; +import com.android.tradefed.targetprep.RunCommandTargetPreparer; import com.android.tradefed.targetprep.suite.SuiteApkInstaller; import com.android.tradefed.testtype.AndroidJUnitTest; @@ -45,6 +48,8 @@ import java.util.List; public class ConfigurationYamlParserTest { private static final String YAML_TEST_CONFIG_1 = "/testconfigs/yaml/test-config.tf_yaml"; + private static final String YAML_TEST_CONFIG_2 = + "/testconfigs/yaml/not-target-preparer.tf_yaml"; private ConfigurationYamlParser mParser; private ConfigurationDef mConfigDef; @@ -84,14 +89,19 @@ public class ConfigurationYamlParserTest { // Dependencies // apk dependencies - assertEquals(2, config.getTargetPreparers().size()); - ITargetPreparer installApk = config.getTargetPreparers().get(0); + assertEquals(5, config.getTargetPreparers().size()); + ITargetPreparer preCommandRunner = config.getTargetPreparers().get(0); + assertTrue(preCommandRunner instanceof RunCommandTargetPreparer); + ITargetPreparer preCommandRunner2 = config.getTargetPreparers().get(1); + assertTrue(preCommandRunner2 instanceof RunCommandTargetPreparer); + + ITargetPreparer installApk = config.getTargetPreparers().get(2); assertTrue(installApk instanceof SuiteApkInstaller); assertThat(((SuiteApkInstaller) installApk).getTestsFileName()) .containsExactly( new File("test.apk"), new File("test2.apk"), new File("test1.apk")); // device file dependencies - ITargetPreparer pushFile = config.getTargetPreparers().get(1); + ITargetPreparer pushFile = config.getTargetPreparers().get(3); assertTrue(pushFile instanceof PushFilePreparer); assertThat(((PushFilePreparer) pushFile).getPushSpecs(null)) .containsExactly( @@ -99,6 +109,8 @@ public class ConfigurationYamlParserTest { new File("tobepushed2.txt"), "/sdcard", new File("tobepushed.txt")); + ITargetPreparer postCommandRunner = config.getTargetPreparers().get(4); + assertTrue(postCommandRunner instanceof RunCommandTargetPreparer); // Result reporters List<ITestInvocationListener> listeners = config.getTestInvocationListeners(); assertTrue(listeners.get(0) instanceof SuiteResultReporter); @@ -123,14 +135,14 @@ public class ConfigurationYamlParserTest { // Dependencies // apk dependencies - assertEquals(2, config.getTargetPreparers().size()); - ITargetPreparer installApk = config.getTargetPreparers().get(0); + assertEquals(5, config.getTargetPreparers().size()); + ITargetPreparer installApk = config.getTargetPreparers().get(2); assertTrue(installApk instanceof SuiteApkInstaller); assertThat(((SuiteApkInstaller) installApk).getTestsFileName()) .containsExactly( new File("test.apk"), new File("test2.apk"), new File("test1.apk")); // device file dependencies - ITargetPreparer pushFile = config.getTargetPreparers().get(1); + ITargetPreparer pushFile = config.getTargetPreparers().get(3); assertTrue(pushFile instanceof PushFilePreparer); assertThat(((PushFilePreparer) pushFile).getPushSpecs(null)) .containsExactly( @@ -145,6 +157,19 @@ public class ConfigurationYamlParserTest { } } + @Test + public void testParseConfig_notTargetPreparer() throws Exception { + try (InputStream res = readFromRes(YAML_TEST_CONFIG_2)) { + mParser.parse(mConfigDef, "source", res, false); + try { + mConfigDef.createConfiguration(); + fail("Should have thrown an exception"); + } catch (ConfigurationException expected) { + + } + } + } + private InputStream readFromRes(String resourceFile) { return getClass().getResourceAsStream(resourceFile); } diff --git a/tests/src/com/android/tradefed/device/TestDeviceTest.java b/tests/src/com/android/tradefed/device/TestDeviceTest.java index e6007fd6f..f4e8aee3f 100644 --- a/tests/src/com/android/tradefed/device/TestDeviceTest.java +++ b/tests/src/com/android/tradefed/device/TestDeviceTest.java @@ -4747,64 +4747,43 @@ public class TestDeviceTest extends TestCase { } /** - * Test {@link TestDevice#doesFileExist(String)} when the file exists on an sdcard from another - * user. + * Test {@link TestDevice#doesFileExist(String)} using content provider when the file is in + * external storage path. */ public void testDoesFileExists_sdcard() throws Exception { - mTestDevice = - new TestableTestDevice() { - @Override - public int getCurrentUser() - throws DeviceNotAvailableException, DeviceRuntimeException { - return 10; - } - }; - injectShellResponse("ls \"/storage/emulated/10/file\"", "file"); + mTestDevice = createTestDevice(); + + TestableTestDevice spy = (TestableTestDevice) Mockito.spy(mTestDevice); + ContentProviderHandler cp = Mockito.mock(ContentProviderHandler.class); + doReturn(cp).when(spy).getContentProvider(); + + final String fakeFile = "/sdcard/file"; + final String targetFilePath = "/storage/emulated/10/file"; + + doReturn("").when(spy).executeShellCommand(Mockito.contains("content query --user 10")); + EasyMock.replay(mMockIDevice); - assertTrue(mTestDevice.doesFileExist("/sdcard/file")); + spy.doesFileExist(fakeFile); EasyMock.verify(mMockIDevice); + + verify(spy, times(1)).getContentProvider(); + verify(cp, times(1)).doesFileExist(targetFilePath); } /** Push a file using the content provider. */ public void testPushFile_contentProvider() throws Exception { - mTestDevice = - new TestableTestDevice() { - @Override - public int getApiLevel() throws DeviceNotAvailableException { - return 29; - } - - @Override - public int getCurrentUser() - throws DeviceNotAvailableException, DeviceRuntimeException { - return 10; - } - - @Override - public boolean isPackageInstalled(String packageName, String userId) - throws DeviceNotAvailableException { - return false; - } - }; + mTestDevice = createTestDevice(); TestableTestDevice spy = (TestableTestDevice) Mockito.spy(mTestDevice); + setupContentProvider(spy); + final String fakeRemotePath = "/sdcard/"; File tmpFile = FileUtil.createTempFile("push", ".test"); - doReturn(null) - .when(spy) - .installPackage(Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean()); - CommandResult setLegacy = new CommandResult(CommandStatus.SUCCESS); - doReturn(setLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops set")); - - CommandResult getLegacy = new CommandResult(CommandStatus.SUCCESS); - getLegacy.setStdout("LEGACY_STORAGE: allow"); - doReturn(getLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops get")); CommandResult writeContent = new CommandResult(CommandStatus.SUCCESS); writeContent.setStdout(""); doReturn(writeContent) .when(spy) .executeShellV2Command(Mockito.contains("content write"), (File) Mockito.any()); - doReturn(null).when(spy).uninstallPackage(Mockito.eq("android.tradefed.contentprovider")); EasyMock.replay(mMockIDevice); try { boolean res = spy.pushFile(tmpFile, fakeRemotePath); @@ -4825,37 +4804,12 @@ public class TestDeviceTest extends TestCase { /** Push a file using the content provider. */ public void testPushFile_contentProvider_notFound() throws Exception { - mTestDevice = - new TestableTestDevice() { - @Override - public int getApiLevel() throws DeviceNotAvailableException { - return 29; - } - - @Override - public int getCurrentUser() - throws DeviceNotAvailableException, DeviceRuntimeException { - return 10; - } - - @Override - public boolean isPackageInstalled(String packageName, String userId) - throws DeviceNotAvailableException { - return false; - } - }; + mTestDevice = createTestDevice(); TestableTestDevice spy = (TestableTestDevice) Mockito.spy(mTestDevice); + setupContentProvider(spy); + final String fakeRemotePath = "/sdcard/"; File tmpFile = FileUtil.createTempFile("push", ".test"); - doReturn(null) - .when(spy) - .installPackage(Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean()); - CommandResult setLegacy = new CommandResult(CommandStatus.SUCCESS); - doReturn(setLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops set")); - - CommandResult getLegacy = new CommandResult(CommandStatus.SUCCESS); - getLegacy.setStdout("LEGACY_STORAGE: allow"); - doReturn(getLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops get")); CommandResult writeContent = new CommandResult(CommandStatus.SUCCESS); writeContent.setStdout(""); @@ -4901,4 +4855,38 @@ public class TestDeviceTest extends TestCase { EasyMock.eq(property))) .andReturn(stubResult); } + + private void setupContentProvider(TestableTestDevice spy) throws Exception { + doReturn(null) + .when(spy) + .installPackage(Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean()); + CommandResult setLegacy = new CommandResult(CommandStatus.SUCCESS); + doReturn(setLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops set")); + + CommandResult getLegacy = new CommandResult(CommandStatus.SUCCESS); + getLegacy.setStdout("LEGACY_STORAGE: allow"); + doReturn(getLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops get")); + + doReturn(null).when(spy).uninstallPackage(Mockito.eq("android.tradefed.contentprovider")); + } + + private TestableTestDevice createTestDevice() { + return new TestableTestDevice() { + @Override + public int getApiLevel() throws DeviceNotAvailableException { + return 29; + } + + @Override + public int getCurrentUser() throws DeviceNotAvailableException, DeviceRuntimeException { + return 10; + } + + @Override + public boolean isPackageInstalled(String packageName, String userId) + throws DeviceNotAvailableException { + return false; + } + }; + } } diff --git a/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java b/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java index e8d0219f6..18109d12a 100644 --- a/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java +++ b/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java @@ -58,6 +58,7 @@ import org.mockito.Mockito; import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -323,6 +324,32 @@ public class RemoteAndroidVirtualDeviceTest { /** Test {@link RemoteAndroidVirtualDevice#postInvocationTearDown(Throwable)}. */ @Test public void testPostInvocationTearDown() throws Exception { + mTestDevice = + new TestableRemoteAndroidVirtualDevice() { + @Override + protected IRunUtil getRunUtil() { + return mMockRunUtil; + } + + @Override + void createGceSshMonitor( + ITestDevice device, + IBuildInfo buildInfo, + HostAndPort hostAndPort, + TestDeviceOptions deviceOptions) { + // ignore + } + + @Override + GceManager getGceHandler() { + return mGceHandler; + } + + @Override + public DeviceDescriptor getDeviceDescriptor() { + return null; + } + }; mTestDevice.setTestLogger(mTestLogger); EasyMock.expect(mMockStateMonitor.waitForDeviceNotAvailable(EasyMock.anyLong())) .andReturn(true); @@ -803,4 +830,83 @@ public class RemoteAndroidVirtualDeviceTest { } verifyMocks(); } + + /** + * Run powerwash() but GceAvdInfo = null, RemoteAndroidVirtualDevice choose to throw exception. + */ + @Test + public void testPowerwashNoAvdInfo() throws Exception { + final String expectedException = "Can not get GCE AVD Info. launch GCE first? [ : ]"; + EasyMock.replay(mMockRunUtil, mMockIDevice); + try { + mTestDevice.powerwashGce(); + fail("Should have thrown an exception"); + } catch (TargetSetupError expected) { + assertEquals(expectedException, expected.getMessage()); + } + EasyMock.verify(mMockRunUtil, mMockIDevice); + } + + /** Test powerwash GCE command */ + @Test + public void testPowerwashGce() throws Exception { + mTestDevice = + new TestableRemoteAndroidVirtualDevice() { + @Override + public IDevice getIDevice() { + return mMockIDevice; + } + + @Override + GceManager getGceHandler() { + return mGceHandler; + } + + @Override + void createGceSshMonitor( + ITestDevice device, + IBuildInfo buildInfo, + HostAndPort hostAndPort, + TestDeviceOptions deviceOptions) { + // ignore + } + }; + String instanceUser = "user1"; + IBuildInfo mMockBuildInfo = EasyMock.createMock(IBuildInfo.class); + OptionSetter setter = new OptionSetter(mTestDevice.getOptions()); + setter.setOptionValue("instance-user", instanceUser); + String powerwashCommand = String.format("/home/%s/bin/powerwash_cvd", instanceUser); + String avdConnectHost = String.format("%s@127.0.0.1", instanceUser); + GceAvdInfo gceAvd = + new GceAvdInfo( + instanceUser, HostAndPort.fromHost("127.0.0.1"), null, GceStatus.SUCCESS); + doReturn(gceAvd).when(mGceHandler).startGce(null); + OutputStream stdout = null; + OutputStream stderr = null; + CommandResult powerwashCmdResult = new CommandResult(CommandStatus.SUCCESS); + EasyMock.expect( + mMockRunUtil.runTimedCmd( + EasyMock.anyLong(), + EasyMock.eq(stdout), + EasyMock.eq(stderr), + EasyMock.eq("ssh"), + EasyMock.eq("-o"), + EasyMock.eq("UserKnownHostsFile=/dev/null"), + EasyMock.eq("-o"), + EasyMock.eq("StrictHostKeyChecking=no"), + EasyMock.eq("-o"), + EasyMock.eq("ServerAliveInterval=10"), + EasyMock.eq("-i"), + EasyMock.anyObject(), + EasyMock.eq(avdConnectHost), + EasyMock.eq(powerwashCommand))) + .andReturn(powerwashCmdResult); + EasyMock.expect(mMockStateMonitor.waitForDeviceAvailable(EasyMock.anyLong())) + .andReturn(mMockIDevice); + EasyMock.replay(mMockRunUtil, mMockIDevice); + // Launch GCE before powerwash. + mTestDevice.launchGce(mMockBuildInfo); + mTestDevice.powerwashGce(); + EasyMock.verify(mMockRunUtil, mMockIDevice); + } } diff --git a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java index b0da7d41d..0570a5988 100644 --- a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java +++ b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java @@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.util.CommandResult; @@ -369,6 +370,36 @@ public class ContentProviderHandlerTest { espacedUrl); } + /** Test {@link ContentProviderHandler#doesFileExist(String)}. */ + @Test + public void testDoesFileExist() throws Exception { + String devicePath = "path/somewhere/file.txt"; + + when(mMockDevice.getCurrentUser()).thenReturn(99); + when(mMockDevice.executeShellCommand( + "content query --user 99 --uri " + + ContentProviderHandler.createEscapedContentUri(devicePath))) + .thenReturn(""); + + assertTrue(mProvider.doesFileExist(devicePath)); + } + + /** + * Test {@link ContentProviderHandler#doesFileExist(String)} returns false when 'adb shell + * content query' returns no results. + */ + @Test + public void testDoesFileExist_NotExists() throws Exception { + String devicePath = "path/somewhere/"; + + when(mMockDevice.getCurrentUser()).thenReturn(99); + when(mMockDevice.executeShellCommand( + "content query --user 99 --uri " + + ContentProviderHandler.createEscapedContentUri(devicePath))) + .thenReturn("No result found.\n"); + assertFalse(mProvider.doesFileExist(devicePath)); + } + @Test public void testParseQueryResultRow() { String row = diff --git a/tests/src/com/android/tradefed/device/metric/BuddyInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/BuddyInfoMetricCollectorTest.java deleted file mode 100644 index 27bf498a7..000000000 --- a/tests/src/com/android/tradefed/device/metric/BuddyInfoMetricCollectorTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; - -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.result.ITestInvocationListener; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; - -import java.io.File; - -/** Unit tests for {@link BuddyInfoMetricCollector}. */ -// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests. -@RunWith(JUnit4.class) -public class BuddyInfoMetricCollectorTest { - @Mock IInvocationContext mContext; - - @Mock ITestInvocationListener mListener; - - @Mock ITestDevice device; - - @Spy BuddyInfoMetricCollector mBuddyInfoMetricCollector; - - @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - - mBuddyInfoMetricCollector.init(mContext, mListener); - - doNothing() - .when(mListener) - .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class)); - - doReturn(new File("unusable-index-1")) - .when(mBuddyInfoMetricCollector) - .saveProcessOutput(any(ITestDevice.class), anyString(), anyString()); - - doReturn(tempFolder.newFolder()).when(mBuddyInfoMetricCollector).createTempDir(); - } - - @Test - public void testCollect() throws Exception { - DeviceMetricData runData = new DeviceMetricData(mContext); - - mBuddyInfoMetricCollector.collect(device, runData); - - // Verify that we logged the metric file. - verify(mListener).testLog(eq("unusable-index-1"), eq(LogDataType.TEXT), any()); - } -} diff --git a/tests/src/com/android/tradefed/device/metric/BugreportzMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/BugreportzMetricCollectorTest.java deleted file mode 100644 index 6b19eb491..000000000 --- a/tests/src/com/android/tradefed/device/metric/BugreportzMetricCollectorTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.result.ITestInvocationListener; -import java.util.Arrays; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; - -/** Unit tests for {@link BugreportzMetricCollector}. */ -@RunWith(JUnit4.class) -public class BugreportzMetricCollectorTest { - @Mock IInvocationContext mContext; - - @Mock ITestDevice mTestDevice; - - @Mock ITestInvocationListener mForwarder; - - @Spy BugreportzMetricCollector mBugreportzMetricCollector; - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - - doReturn(Arrays.asList(mTestDevice)).when(mContext).getDevices(); - - when(mTestDevice.logBugreport(anyString(), any(ITestInvocationListener.class))) - .thenReturn(true); - - mBugreportzMetricCollector.init(mContext, mForwarder); - - when(mBugreportzMetricCollector.getFileSuffix()).thenReturn("1"); - } - - /** Tests successful collection of bugreport. */ - @Test - public void testCollect_success() throws Exception { - when(mTestDevice.logBugreport("bugreportz-1", mForwarder)).thenReturn(true); - - DeviceMetricData runData = new DeviceMetricData(mContext); - - mBugreportzMetricCollector.collect(mTestDevice, runData); - - verify(mTestDevice).logBugreport(eq("bugreport-1"), eq(mForwarder)); - } -} diff --git a/tests/src/com/android/tradefed/device/metric/DumpHeapCollectorTest.java b/tests/src/com/android/tradefed/device/metric/DumpHeapCollectorTest.java deleted file mode 100644 index 6a9cb5681..000000000 --- a/tests/src/com/android/tradefed/device/metric/DumpHeapCollectorTest.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.android.tradefed.config.OptionSetter; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.result.ITestInvocationListener; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; -import com.android.tradefed.util.FileUtil; -import com.google.common.truth.Truth; -import java.io.File; -import java.util.Arrays; -import java.util.List; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; - -/** Unit tests for {@link DumpHeapCollector}. */ -@RunWith(JUnit4.class) -public class DumpHeapCollectorTest { - - @Mock private IInvocationContext mContext; - - @Mock private ITestInvocationListener mListener; - - @Mock private ITestDevice mDevice; - - @Rule public TemporaryFolder folder = new TemporaryFolder(); - - @Spy private DumpHeapCollector mDumpheapCollector; - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - - when(mDevice.executeShellCommand("dumpsys meminfo -c | grep camera")) - .thenReturn("proc,native,camera,21348,800,N/A,e\n"); - - when(mDevice.executeShellCommand("dumpsys meminfo -c | grep maps")) - .thenReturn("proc,native,maps,21349,900,N/A,e\n"); - - when(mDevice.executeShellCommand("am dumpheap camera /data/local/tmp/camera_trigger.hprof")) - .thenReturn(""); - - doNothing() - .when(mListener) - .testLog(anyString(), any(LogDataType.class), any(InputStreamSource.class)); - } - - @Test - public void testTakeDumpheap_success() throws Exception { - File mapsDumpheap1 = folder.newFile("maps1"); - File mapsDumpheap2 = folder.newFile("maps2"); - - doReturn("1").when(mDumpheapCollector).getFileSuffix(); - - when(mDevice.dumpHeap("maps", "/data/local/tmp/maps_trigger_1.hprof")) - .thenReturn(mapsDumpheap1) - .thenReturn(mapsDumpheap2); - - String fakeDumpheapOutput = - "proc,native,maps,21349,900,N/A,e\nproc,native,camera,21350,800,N/A,e\n"; - - List<File> files = - mDumpheapCollector.takeDumpheap(mDevice, fakeDumpheapOutput, "maps", 850L); - - Truth.assertThat(files).containsExactly(mapsDumpheap1); - } - - @Test - public void testCollect_success() throws Exception { - File tempFile1 = folder.newFile(); - File tempFile2 = folder.newFile(); - when(mDevice.dumpHeap(anyString(), anyString())) - .thenReturn(tempFile1) - .thenReturn(tempFile2); - - OptionSetter options = new OptionSetter(mDumpheapCollector); - - options.setOptionValue("dumpheap-thresholds", "camera", "700"); - options.setOptionValue("dumpheap-thresholds", "maps", "800"); - - mDumpheapCollector.init(mContext, mListener); - - mDumpheapCollector.collect(mDevice, null); - - ArgumentCaptor<String> dataNameCaptor = ArgumentCaptor.forClass(String.class); - ArgumentCaptor<LogDataType> dataTypeCaptor = ArgumentCaptor.forClass(LogDataType.class); - ArgumentCaptor<InputStreamSource> inputCaptor = - ArgumentCaptor.forClass(InputStreamSource.class); - - verify(mListener, times(2)) - .testLog(dataNameCaptor.capture(), dataTypeCaptor.capture(), inputCaptor.capture()); - - // Assert that the correct filename was sent to testLog. - Truth.assertThat(dataNameCaptor.getAllValues()) - .containsExactlyElementsIn( - Arrays.asList( - FileUtil.getBaseName(tempFile1.getName()), - FileUtil.getBaseName(tempFile2.getName()))); - - // Assert that the correct data type was sent to testLog. - Truth.assertThat(dataTypeCaptor.getAllValues()) - .containsExactlyElementsIn(Arrays.asList(LogDataType.HPROF, LogDataType.HPROF)); - } - - @Test - public void testCollectSuccess_thresholdTooHigh() throws Exception { - File tempFile1 = folder.newFile(); - File tempFile2 = folder.newFile(); - when(mDevice.pullFile(anyString())).thenReturn(tempFile1).thenReturn(tempFile2); - - OptionSetter options = new OptionSetter(mDumpheapCollector); - - options.setOptionValue("dumpheap-thresholds", "camera", "7000"); - options.setOptionValue("dumpheap-thresholds", "maps", "8000"); - - mDumpheapCollector.init(mContext, mListener); - - mDumpheapCollector.collect(mDevice, null); - - ArgumentCaptor<String> dataNameCaptor = ArgumentCaptor.forClass(String.class); - ArgumentCaptor<LogDataType> dataTypeCaptor = ArgumentCaptor.forClass(LogDataType.class); - ArgumentCaptor<InputStreamSource> inputCaptor = - ArgumentCaptor.forClass(InputStreamSource.class); - - verify(mListener, times(0)) - .testLog(dataNameCaptor.capture(), dataTypeCaptor.capture(), inputCaptor.capture()); - } - - @Test - public void testCollectNoError_processNotFound() throws Exception { - // Make the meminfo dump not contain the heap info of fake_process. - when(mDevice.executeShellCommand("dumpsys meminfo -c | grep fake_process")).thenReturn(""); - - OptionSetter options = new OptionSetter(mDumpheapCollector); - options.setOptionValue("dumpheap-thresholds", "fake_process", "7000"); - - mDumpheapCollector.init(mContext, mListener); - - mDumpheapCollector.collect(mDevice, null); - - ArgumentCaptor<String> dataNameCaptor = ArgumentCaptor.forClass(String.class); - ArgumentCaptor<LogDataType> dataTypeCaptor = ArgumentCaptor.forClass(LogDataType.class); - ArgumentCaptor<InputStreamSource> inputCaptor = - ArgumentCaptor.forClass(InputStreamSource.class); - - // Verify that no testLog calls were made. - verify(mListener, times(0)) - .testLog(dataNameCaptor.capture(), dataTypeCaptor.capture(), inputCaptor.capture()); - } -} - diff --git a/tests/src/com/android/tradefed/device/metric/GraphicsStatsMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/GraphicsStatsMetricCollectorTest.java deleted file mode 100644 index cb051a600..000000000 --- a/tests/src/com/android/tradefed/device/metric/GraphicsStatsMetricCollectorTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; - -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.result.ITestInvocationListener; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; - -import java.io.File; - -/** Unit tests for {@link GraphicsStatsMetricCollector}. */ -//TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests. -@RunWith(JUnit4.class) -public class GraphicsStatsMetricCollectorTest { - @Mock IInvocationContext mContext; - - @Mock ITestInvocationListener mListener; - - @Mock ITestDevice mDevice; - - @Spy GraphicsStatsMetricCollector mGfxInfoMetricCollector; - - @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - - mGfxInfoMetricCollector.init(mContext, mListener); - - doNothing() - .when(mListener) - .testLog(anyString(), eq(LogDataType.GFX_INFO), any(InputStreamSource.class)); - - doReturn(new File("graphics-1")) - .when(mGfxInfoMetricCollector) - .saveProcessOutput(any(ITestDevice.class), anyString(), anyString()); - - doReturn(tempFolder.newFolder()).when(mGfxInfoMetricCollector).createTempDir(); - } - - @Test - public void testCollect() throws Exception { - DeviceMetricData runData = new DeviceMetricData(mContext); - - mGfxInfoMetricCollector.collect(mDevice, runData); - - // Verify that we logged the metric file. - verify(mListener).testLog(eq("graphics-1"), eq(LogDataType.GFX_INFO), any()); - } -} diff --git a/tests/src/com/android/tradefed/device/metric/IonHeapInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/IonHeapInfoMetricCollectorTest.java deleted file mode 100644 index 2547672ae..000000000 --- a/tests/src/com/android/tradefed/device/metric/IonHeapInfoMetricCollectorTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tradefed.device.metric; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; - -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.result.ITestInvocationListener; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; - -import java.io.File; - -/** Unit tests for {@link IonHeapInfoMetricCollector}. */ -// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests. -@RunWith(JUnit4.class) -public class IonHeapInfoMetricCollectorTest { - @Mock IInvocationContext mContext; - - @Mock ITestInvocationListener mListener; - - @Mock ITestDevice mDevice; - - @Spy IonHeapInfoMetricCollector mIonHeapInfoMetricCollector; - - @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - - mIonHeapInfoMetricCollector.init(mContext, mListener); - - doNothing() - .when(mListener) - .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class)); - - doReturn(new File("ion-system-2")) - .when(mIonHeapInfoMetricCollector) - .saveProcessOutput( - any(ITestDevice.class), eq("cat /d/ion/heaps/system"), anyString()); - doReturn(new File("ion-audio-1")) - .when(mIonHeapInfoMetricCollector) - .saveProcessOutput( - any(ITestDevice.class), eq("cat /d/ion/heaps/audio"), anyString()); - - doReturn(tempFolder.newFolder()).when(mIonHeapInfoMetricCollector).createTempDir(); - } - - @Test - public void testCollect() throws Exception { - DeviceMetricData runData = new DeviceMetricData(mContext); - - mIonHeapInfoMetricCollector.collect(mDevice, runData); - - // Verify that we logged the metric file. - verify(mListener).testLog(eq("ion-system-2"), eq(LogDataType.TEXT), any()); - verify(mListener).testLog(eq("ion-audio-1"), eq(LogDataType.TEXT), any()); - } -} diff --git a/tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java b/tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java index 164e536d3..db9f1e6db 100644 --- a/tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java +++ b/tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java @@ -347,6 +347,49 @@ public class JavaCodeCoverageCollectorTest { mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric)); } + @Test + public void testRunningProcess_coverageFileNotDeleted() throws Exception { + enableJavaCoverage(); + + List<String> coverageFileList = + ImmutableList.of( + "/data/misc/trace/coverage1.ec", + "/data/misc/trace/coverage2.ec", + "/data/misc/trace/jacoco-123.mm.ec", + "/data/misc/trace/jacoco-456.mm.ec"); + String psOutput = + "USER PID PPID VSZ RSS WCHAN PC S NAME\n" + + "bluetooth 123 1366 123 456 SyS_epoll+ 0 S com.android.bluetooth\n" + + "radio 890 1 7890 123 binder_io+ 0 S com.android.phone\n" + + "root 11 1234 567 890 binder_io+ 0 S not.a.java.package\n"; + + // Setup mocks. + mockCoverageFileOnDevice(DEVICE_PATH); + + for (String additionalFile : coverageFileList) { + mockCoverageFileOnDevice(additionalFile); + } + + doReturn("").when(mMockDevice).executeShellCommand("pm list packages -a"); + doReturn(psOutput).when(mMockDevice).executeShellCommand("ps -e"); + doReturn(String.join("\n", coverageFileList)) + .when(mMockDevice) + .executeShellCommand("find /data/misc/trace -name '*.ec'"); + + // Simulate a test run. + mCodeCoverageCollector.init(mMockContext, mFakeListener); + mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT); + Map<String, String> metric = new HashMap<>(); + metric.put("coverageFilePath", DEVICE_PATH); + mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric)); + + // Verify the correct files were deleted and some files were not deleted. + verify(mMockDevice).deleteFile(coverageFileList.get(0)); + verify(mMockDevice).deleteFile(coverageFileList.get(1)); + verify(mMockDevice, never()).deleteFile(coverageFileList.get(2)); + verify(mMockDevice).deleteFile(coverageFileList.get(3)); + } + private void mockCoverageFileOnDevice(String devicePath) throws IOException, DeviceNotAvailableException { File coverageFile = folder.newFile(new File(devicePath).getName()); diff --git a/tests/src/com/android/tradefed/device/metric/MemInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/MemInfoMetricCollectorTest.java deleted file mode 100644 index c56a081a8..000000000 --- a/tests/src/com/android/tradefed/device/metric/MemInfoMetricCollectorTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.result.ITestInvocationListener; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; - -import java.io.File; - -/** Unit tests for {@link MemInfoMetricCollector}. */ -// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests. -@RunWith(JUnit4.class) -public class MemInfoMetricCollectorTest { - @Mock IInvocationContext mContext; - - @Mock ITestInvocationListener mListener; - - @Mock ITestDevice mDevice; - - @Spy MemInfoMetricCollector mMemInfoMetricCollector; - - @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - - mMemInfoMetricCollector.init(mContext, mListener); - - doNothing() - .when(mListener) - .testLog( - anyString(), eq(LogDataType.COMPACT_MEMINFO), any(InputStreamSource.class)); - - doReturn(new File("compact-meminfo-1")) - .when(mMemInfoMetricCollector) - .saveProcessOutput(any(ITestDevice.class), anyString(), anyString()); - - doReturn(tempFolder.newFolder()).when(mMemInfoMetricCollector).createTempDir(); - } - - @Test - public void testCollect() throws Exception { - DeviceMetricData runData = new DeviceMetricData(mContext); - when(mMemInfoMetricCollector.getFileSuffix()).thenReturn("1"); - - mMemInfoMetricCollector.collect(mDevice, runData); - - // Verify that we logged the metric file. - verify(mListener).testLog(eq("compact-meminfo-1"), eq(LogDataType.COMPACT_MEMINFO), any()); - } -} diff --git a/tests/src/com/android/tradefed/device/metric/PagetypeInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/PagetypeInfoMetricCollectorTest.java deleted file mode 100644 index 934874365..000000000 --- a/tests/src/com/android/tradefed/device/metric/PagetypeInfoMetricCollectorTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.result.ITestInvocationListener; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; - -import java.io.File; - -/** Unit tests for {@link PagetypeInfoMetricCollector}. */ -// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests. -@RunWith(JUnit4.class) -public class PagetypeInfoMetricCollectorTest { - @Mock IInvocationContext mContext; - - @Mock ITestInvocationListener mListener; - - @Mock ITestDevice mDevice; - - @Spy PagetypeInfoMetricCollector mPagetypeInfoMetricCollector; - - @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - - mPagetypeInfoMetricCollector.init(mContext, mListener); - - doNothing() - .when(mListener) - .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class)); - - doReturn(new File("pagetypeinfo-1")) - .when(mPagetypeInfoMetricCollector) - .saveProcessOutput(any(ITestDevice.class), anyString(), anyString()); - - doReturn(tempFolder.newFolder()).when(mPagetypeInfoMetricCollector).createTempDir(); - } - - @Test - public void testCollect() throws Exception { - DeviceMetricData runData = new DeviceMetricData(mContext); - when(mPagetypeInfoMetricCollector.getFileSuffix()).thenReturn("1"); - - mPagetypeInfoMetricCollector.collect(mDevice, runData); - - // Verify that we logged the metric file. - verify(mListener).testLog(eq("pagetypeinfo-1"), eq(LogDataType.TEXT), any()); - } -} diff --git a/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java index a9807b0d3..9d131c5c1 100644 --- a/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java +++ b/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java @@ -16,6 +16,7 @@ package com.android.tradefed.device.metric; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.times; @@ -120,7 +121,45 @@ public class PerfettoPullerMetricCollectorTest { assertTrue("Trace duration metrics not available but expected.", currentMetrics.get("perfetto_trace_extractor_runtime").getMeasurements() .getSingleDouble() >= 0); - assertTrue("Trace file size metric is not available.", + assertNull("Trace duration metrics not available but expected.", + currentMetrics.get("perfetto_trace_file_size_bytes")); + } + + @Test + public void testProcessingFlowWithFileSizeMetric() throws Exception { + + OptionSetter setter = new OptionSetter(mPerfettoMetricCollector); + setter.setOptionValue("pull-pattern-keys", "perfettofile"); + setter.setOptionValue("perfetto-binary-path", "trx"); + setter.setOptionValue("convert-metric-file", "false"); + setter.setOptionValue("collect-perfetto-file-size", "true"); + HashMap<String, Metric> currentMetrics = new HashMap<>(); + currentMetrics.put("perfettofile", TfMetricProtoUtil.stringToMetric("/data/trace.pb")); + Mockito.when(mMockDevice.pullFile(Mockito.eq("/data/trace.pb"))) + .thenReturn(new File("trace")); + + TestDescription testDesc = new TestDescription("xyz", "abc"); + CommandResult cr = new CommandResult(); + cr.setStatus(CommandStatus.SUCCESS); + cr.setStdout("abc:efg"); + + Mockito.doReturn(cr).when(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(), + Mockito.any(), Mockito.any(), Mockito.any()); + + mPerfettoMetricCollector.testStarted(testDesc); + mPerfettoMetricCollector.testEnded(testDesc, currentMetrics); + + Mockito.verify(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(), + Mockito.any(), Mockito.any(), Mockito.any()); + Mockito.verify(mMockListener) + .testLog(Mockito.eq("trace"), Mockito.eq(LogDataType.PERFETTO), Mockito.any()); + assertTrue("Expected two metrics that includes success status", + currentMetrics.get("perfetto_trace_extractor_status").getMeasurements() + .getSingleString().equals("1")); + assertTrue("Trace duration metrics not available but expected.", + currentMetrics.get("perfetto_trace_extractor_runtime").getMeasurements() + .getSingleDouble() >= 0); + assertTrue("Trace file size metric is not available in the final metrics.", currentMetrics.get("perfetto_trace_file_size_bytes").getMeasurements() .getSingleDouble() >= 0); } @@ -331,3 +370,4 @@ public class PerfettoPullerMetricCollectorTest { } } + diff --git a/tests/src/com/android/tradefed/device/metric/ProcessMaxMemoryCollectorTest.java b/tests/src/com/android/tradefed/device/metric/ProcessMaxMemoryCollectorTest.java deleted file mode 100644 index b66138f76..000000000 --- a/tests/src/com/android/tradefed/device/metric/ProcessMaxMemoryCollectorTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.android.tradefed.config.OptionSetter; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; -import com.android.tradefed.result.ITestInvocationListener; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mockito; - -import java.util.Collections; -import java.util.HashMap; - -/** Unit tests for {@link ProcessMaxMemoryCollector}. */ -@RunWith(JUnit4.class) -public class ProcessMaxMemoryCollectorTest { - - private ProcessMaxMemoryCollector mCollector; - private IInvocationContext mContext; - private ITestDevice mDevice; - private ITestInvocationListener mListener; - - private static final String TEST_INPUT = - "time,28506638,177086152\n" - + "4,938,system_server,11,22,N/A,44,0,0,N/A,0,0,0,N/A,0,27613,14013,176602," - + "218228,0,0,122860,122860,1512,1412,5740,8664,0,0,154924,154924,27568," - + "13972,11916,53456,0,0,123008,123008,0,0,0,0,0,0,0,0,Dalvik Other,3662,0," - + "104,0,3660,0,0,0,Stack,1576,0,8,0,1576,0,0,0,Cursor,0,0,0,0,0,0,0,0," - + "Ashmem,156,0,20,0,148,0,0,0,Gfx dev,100,0,48,0,76,0,0,0,Other dev,116,0," - + "164,0,0,96,0,0,.so mmap,7500,2680,3984,21864,904,2680,0,0,.jar mmap,0,0,0," - + "0,0,0,0,0,.apk mmap,72398,71448,0,11736,0,71448,0,0,.ttf mmap,0,0,0,0,0,0," - + "0,0,.dex mmap,76874,46000,0,83644,40,46000,0,0,.oat mmap,8127,2684,64," - + "26652,0,2684,0,0,.art mmap,1991,48,972,10004,1544,48,0,0,Other mmap,137,0," - + "44,1024,4,52,0,0,EGL mtrack,0,0,0,0,0,0,0,0,GL mtrack,111,222,333,444,555," - + "666,777,888,"; - - @Before - public void setup() throws Exception { - mCollector = new ProcessMaxMemoryCollector(); - mContext = mock(IInvocationContext.class); - mDevice = mock(ITestDevice.class); - when(mContext.getDevices()).thenReturn(Collections.singletonList(mDevice)); - mListener = mock(ITestInvocationListener.class); - mCollector.init(mContext, mListener); - OptionSetter setter = new OptionSetter(mCollector); - setter.setOptionValue("memory-usage-process-name", "system_server"); - } - - @Test - public void testCollector() throws Exception { - when(mDevice.executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server"))) - .thenReturn(TEST_INPUT); - - DeviceMetricData data = new DeviceMetricData(mContext); - mCollector.onStart(data); - mCollector.collect(mDevice, data); - mCollector.onEnd(data); - - verify(mDevice).executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server")); - - HashMap<String, Metric> results = new HashMap<>(); - data.addToMetrics(results); - assertEquals(218228, results.get("MAX_PSS#system_server").getMeasurements().getSingleInt()); - assertEquals(53456, results.get("MAX_USS#system_server").getMeasurements().getSingleInt()); - } - - @Test - public void testCollectorNoProcess() throws Exception { - when(mDevice.executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server"))) - .thenReturn("No process found for: system_server"); - - DeviceMetricData data = new DeviceMetricData(mContext); - mCollector.onStart(data); - mCollector.collect(mDevice, data); - mCollector.onEnd(data); - - verify(mDevice).executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server")); - - HashMap<String, Metric> results = new HashMap<>(); - data.addToMetrics(results); - assertTrue(results.isEmpty()); - } -} diff --git a/tests/src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollectorTest.java deleted file mode 100644 index 13605f388..000000000 --- a/tests/src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollectorTest.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import com.android.tradefed.config.OptionSetter; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.invoker.InvocationContext; -import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements; -import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; -import com.android.tradefed.result.ITestInvocationListener; -import com.android.tradefed.util.RunUtil; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** Unit tests for {@link ScheduleMultipleDeviceMetricCollector}. */ -@RunWith(JUnit4.class) -public class ScheduleMultipleDeviceMetricCollectorTest { - @Rule public final TemporaryFolder folder = new TemporaryFolder(); - @Mock private ITestDevice mTestDevice; - @Mock private ITestInvocationListener mMockListener; - @Spy private ScheduleMultipleDeviceMetricCollector mMultipleMetricCollector; - - private IInvocationContext mContext; - - static class TestMeminfoCollector extends ScheduledDeviceMetricCollector { - private int mInternalCounter = 0; - private String key = "meminfo"; - - TestMeminfoCollector() { - setTag("meminfoInterval"); - } - - @Override - public void collect(ITestDevice device, DeviceMetricData runData) - throws InterruptedException { - mInternalCounter++; - runData.addMetricForDevice( - device, - key + mInternalCounter, - Metric.newBuilder() - .setMeasurements( - Measurements.newBuilder() - .setSingleString("value" + mInternalCounter))); - } - } - - static class TestJankinfoCollector extends ScheduledDeviceMetricCollector { - private int mInternalCounter = 0; - private String key = "jankinfo"; - - TestJankinfoCollector() { - setTag("jankInterval"); - } - - @Override - public void collect(ITestDevice device, DeviceMetricData runData) - throws InterruptedException { - mInternalCounter++; - runData.addMetricForDevice( - device, - key + mInternalCounter, - Metric.newBuilder() - .setMeasurements( - Measurements.newBuilder() - .setSingleString("value" + mInternalCounter))); - } - } - - static class TestFragmentationCollector extends ScheduledDeviceMetricCollector { - private int mInternalCounter = 0; - private String key = "fragmentation"; - - TestFragmentationCollector() { - setTag("fragmentationInterval"); - } - - @Override - public void collect(ITestDevice device, DeviceMetricData runData) - throws InterruptedException { - mInternalCounter++; - runData.addMetricForDevice( - device, - key + mInternalCounter, - Metric.newBuilder() - .setMeasurements( - Measurements.newBuilder() - .setSingleString("value" + mInternalCounter))); - } - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mContext = new InvocationContext(); - mContext.addAllocatedDevice("test device", mTestDevice); - } - - @Test - public void testMultipleMetricCollector_success() throws Exception { - OptionSetter setter = new OptionSetter(mMultipleMetricCollector); - - // Set up the metric collection storage path. - File metricStoragePath = folder.newFolder(); - setter.setOptionValue("metric-storage-path", metricStoragePath.toString()); - - // Set up the intervals. - Map<String, Long> intervals = new HashMap<>(); - intervals.put("meminfoInterval", 100L); - intervals.put("fragmentationInterval", 100L); - intervals.put("jankInterval", 100L); - for (String key : intervals.keySet()) { - setter.setOptionValue( - "metric-collection-intervals", key, intervals.get(key).toString()); - } - - // Request the collectors. - List<String> classnames = new ArrayList<>(); - classnames.add(TestMeminfoCollector.class.getName()); - classnames.add(TestJankinfoCollector.class.getName()); - classnames.add(TestFragmentationCollector.class.getName()); - for (String key : classnames) { - setter.setOptionValue("metric-collector-command-classes", key); - } - - DeviceMetricData runData = new DeviceMetricData(mContext); - - // Start the tests. - HashMap<String, Metric> metrics = new HashMap<>(); - mMultipleMetricCollector.init(mContext, mMockListener); - try { - mMultipleMetricCollector.onTestRunStart(runData); - RunUtil.getDefault().sleep(500); - } finally { - mMultipleMetricCollector.onTestRunEnd(runData, metrics); - } - - // We give it 500msec to run and 100msec interval we should easily have at least run all the - // metrics once. - // assert that the metrics contains filenames of all the collected metrics. - HashMap<String, Metric> metricsCollected = new HashMap<>(); - runData.addToMetrics(metricsCollected); - - assertTrue(metricsCollected.containsKey("jankinfo1")); - assertTrue(metricsCollected.containsKey("meminfo1")); - assertTrue(metricsCollected.containsKey("fragmentation1")); - } - - @Test - public void testMultipleMetricCollector_noFailureEvenIfNoCollectorRequested() throws Exception { - HashMap<String, Metric> metrics = new HashMap<>(); - mMultipleMetricCollector.init(mContext, mMockListener); - - DeviceMetricData runData = new DeviceMetricData(mContext); - - try { - mMultipleMetricCollector.onTestRunStart(runData); - RunUtil.getDefault().sleep(500); - } finally { - mMultipleMetricCollector.onTestRunEnd(runData, metrics); - } - - // No metrics should have been collected. - HashMap<String, Metric> metricsCollected = new HashMap<>(); - runData.addToMetrics(metricsCollected); - - assertEquals(0, metricsCollected.size()); - } - - /** Test that if a specified collector does not exists, we ignore it and proceed. */ - @Test - public void testMultipleMetricCollector_collectorNotFound() throws Exception { - OptionSetter setter = new OptionSetter(mMultipleMetricCollector); - - // Set up the metric collection storage path. - File metricStoragePath = folder.newFolder(); - setter.setOptionValue("metric-storage-path", metricStoragePath.toString()); - - // Set up the intervals. - Map<String, Long> intervals = new HashMap<>(); - intervals.put("meminfoInterval", 100L); - for (String key : intervals.keySet()) { - setter.setOptionValue( - "metric-collection-intervals", key, intervals.get(key).toString()); - } - - // Request the collectors. - List<String> classnames = new ArrayList<>(); - classnames.add(TestMeminfoCollector.class.getName()); - classnames.add("this.does.not.exists.collector"); - for (String key : classnames) { - setter.setOptionValue("metric-collector-command-classes", key); - } - - HashMap<String, Metric> metrics = new HashMap<>(); - mMultipleMetricCollector.init(mContext, mMockListener); - - DeviceMetricData runData = new DeviceMetricData(mContext); - - try { - mMultipleMetricCollector.onTestRunStart(runData); - RunUtil.getDefault().sleep(500); - } finally { - mMultipleMetricCollector.onTestRunEnd(runData, metrics); - } - - // No metrics should have been collected. - HashMap<String, Metric> metricsCollected = new HashMap<>(); - runData.addToMetrics(metricsCollected); - - assertTrue(metricsCollected.containsKey("meminfo1")); - } - - @Test - public void testMultipleMetricCollector_failsForNonNegativeInterval() throws Exception { - String expectedStderr = - "class com.android.tradefed.device.metric." - + "ScheduleMultipleDeviceMetricCollectorTest$TestJankinfoCollector expects " - + "a non negative interval."; - - OptionSetter setter = new OptionSetter(mMultipleMetricCollector); - - // Set up the metric collection storage path. - setter.setOptionValue("metric-storage-path", folder.newFolder().toString()); - - // Set up the interval. - Map<String, Long> intervals = new HashMap<>(); - intervals.put("jankInterval", -100L); - for (String key : intervals.keySet()) { - setter.setOptionValue( - "metric-collection-intervals", key, intervals.get(key).toString()); - } - - // Set up the classname. - List<String> classnames = new ArrayList<>(); - classnames.add(TestJankinfoCollector.class.getName()); - for (String key : classnames) { - setter.setOptionValue("metric-collector-command-classes", key); - } - - DeviceMetricData runData = new DeviceMetricData(mContext); - - // Start the tests, which should fail with the expected error message. - mMultipleMetricCollector.init(mContext, mMockListener); - - try { - mMultipleMetricCollector.onTestRunStart(runData); - fail("Should throw illegal argument exception in case of negative intervals."); - } catch (IllegalArgumentException e) { - assertEquals(expectedStderr, e.getMessage()); - } - } -} diff --git a/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java deleted file mode 100644 index 1f23a433e..000000000 --- a/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; - -import com.android.tradefed.config.OptionSetter; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.invoker.InvocationContext; -import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements; -import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; -import com.android.tradefed.result.ITestInvocationListener; -import com.android.tradefed.util.RunUtil; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mockito; - -import java.util.HashMap; -import java.util.Map; - -/** Unit tests for {@link ScheduledDeviceMetricCollector}. */ -@RunWith(JUnit4.class) -public class ScheduledDeviceMetricCollectorTest { - private Map<String, ITestDevice> mDevicesWithNames = new HashMap<>(); - - public static class TestableAsyncTimer extends ScheduledDeviceMetricCollector { - private int mInternalCounter = 0; - - @Override - void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException { - mInternalCounter++; - runData.addMetricForDevice( - device, - "key" + mInternalCounter, - Metric.newBuilder() - .setMeasurements( - Measurements.newBuilder() - .setSingleString("value" + mInternalCounter))); - } - } - - private TestableAsyncTimer mBase; - private IInvocationContext mContext; - private ITestInvocationListener mMockListener; - - @Before - public void setUp() { - mBase = new TestableAsyncTimer(); - mContext = new InvocationContext(); - mMockListener = Mockito.mock(ITestInvocationListener.class); - } - - /** Test the periodic run of the collector once testRunStarted has been called. */ - @Test - public void testSetupAndPeriodicRunSingleDevice() throws Exception { - // Setup the context with the devices. - mDevicesWithNames.put("test device 1", mock(ITestDevice.class)); - mContext.addAllocatedDevice(mDevicesWithNames); - - OptionSetter setter = new OptionSetter(mBase); - // 100 ms interval - setter.setOptionValue("interval", "100"); - HashMap<String, Metric> metrics = new HashMap<>(); - mBase.init(mContext, mMockListener); - try { - mBase.testRunStarted("testRun", 1); - RunUtil.getDefault().sleep(500); - } finally { - mBase.testRunEnded(0l, metrics); - } - // We give it 500msec to run and 100msec interval we should easily have at least three - // iterations - assertTrue(metrics.containsKey("key1")); - assertTrue(metrics.containsKey("key2")); - assertTrue(metrics.containsKey("key3")); - } - - /** - * Test the periodic run of the collector on multiple devices once testRunStarted has been - * called. - */ - @Test - public void testSetupAndPeriodicRunMultipleDevices() throws Exception { - // Setup the context with the devices. - mDevicesWithNames.put("test device 1", mock(ITestDevice.class)); - mDevicesWithNames.put("test device 2", mock(ITestDevice.class)); - mContext.addAllocatedDevice(mDevicesWithNames); - - OptionSetter setter = new OptionSetter(mBase); - // 100 ms interval - setter.setOptionValue("interval", "100"); - HashMap<String, Metric> metrics = new HashMap<>(); - mBase.init(mContext, mMockListener); - try { - mBase.testRunStarted("testRun", 1); - RunUtil.getDefault().sleep(500); - } finally { - mBase.testRunEnded(0l, metrics); - } - // We give it 500msec to run and 100msec interval we should easily have at least two - // iterations one for each device. The order of execution is arbitrary so check for prefix - // only. - assertTrue(metrics.keySet().stream().anyMatch(key -> key.startsWith("{test device 1}"))); - assertTrue(metrics.keySet().stream().anyMatch(key -> key.startsWith("{test device 2}"))); - } -} diff --git a/tests/src/com/android/tradefed/device/metric/TemperatureCollectorTest.java b/tests/src/com/android/tradefed/device/metric/TemperatureCollectorTest.java deleted file mode 100644 index a4c855024..000000000 --- a/tests/src/com/android/tradefed/device/metric/TemperatureCollectorTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.android.tradefed.config.OptionSetter; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; -import com.android.tradefed.result.ITestInvocationListener; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.util.Collections; -import java.util.HashMap; - -/** Unit tests for {@link TemperatureCollector}. */ -@RunWith(JUnit4.class) -public class TemperatureCollectorTest { - - private TemperatureCollector mCollector; - private IInvocationContext mContext; - private ITestDevice mDevice; - private ITestInvocationListener mListener; - - @Before - public void setup() throws Exception { - mCollector = new TemperatureCollector(); - mContext = mock(IInvocationContext.class); - mDevice = mock(ITestDevice.class); - when(mDevice.isAdbRoot()).thenReturn(true); - when(mContext.getDevices()).thenReturn(Collections.singletonList(mDevice)); - mListener = mock(ITestInvocationListener.class); - mCollector.init(mContext, mListener); - OptionSetter setter = new OptionSetter(mCollector); - setter.setOptionValue( - "device-temperature-file-path", "/sys/class/hwmon/hwmon1/device/msm_therm"); - } - - @Test - public void testCollector() throws Exception { - when(mDevice.executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm"))) - .thenReturn("Result:32 Raw:7e51", "Result:22 Raw:7b51"); - - DeviceMetricData data = new DeviceMetricData(mContext); - mCollector.onStart(data); - mCollector.collect(mDevice, data); - mCollector.collect(mDevice, data); - mCollector.onEnd(data); - - verify(mDevice, times(2)) - .executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm")); - - HashMap<String, Metric> results = new HashMap<>(); - data.addToMetrics(results); - assertEquals(32D, results.get("max_temperature").getMeasurements().getSingleDouble(), 0); - assertEquals(22D, results.get("min_temperature").getMeasurements().getSingleDouble(), 0); - } - - @Test - public void testCollectorNoData() throws Exception { - when(mDevice.executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm"))) - .thenReturn( - "cat: /sys/class/hwmon/hwmon1/device/msm_therm: No such file or directory"); - - DeviceMetricData data = new DeviceMetricData(mContext); - mCollector.onStart(data); - mCollector.collect(mDevice, data); - mCollector.onEnd(data); - - verify(mDevice).executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm")); - - HashMap<String, Metric> results = new HashMap<>(); - data.addToMetrics(results); - assertTrue(results.isEmpty()); - } -} diff --git a/tests/src/com/android/tradefed/device/metric/TraceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/TraceMetricCollectorTest.java deleted file mode 100644 index ffb3727c5..000000000 --- a/tests/src/com/android/tradefed/device/metric/TraceMetricCollectorTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.device.metric; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.result.ITestInvocationListener; -import com.android.tradefed.result.InputStreamSource; -import com.android.tradefed.result.LogDataType; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; - -import java.io.File; - -/** Unit tests for {@link TraceMetricCollector}. */ -// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests. -@RunWith(JUnit4.class) -public class TraceMetricCollectorTest { - @Mock IInvocationContext mContext; - - @Mock ITestInvocationListener mListener; - - @Mock ITestDevice mDevice; - - @Spy TraceMetricCollector mTraceInfoMetricCollector; - - @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - - mTraceInfoMetricCollector.init(mContext, mListener); - - doNothing() - .when(mListener) - .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class)); - - doReturn(new File("trace-1")) - .when(mTraceInfoMetricCollector) - .saveProcessOutput(any(ITestDevice.class), anyString(), anyString()); - - doReturn(tempFolder.newFolder()).when(mTraceInfoMetricCollector).createTempDir(); - } - - @Test - public void testCollect() throws Exception { - DeviceMetricData runData = new DeviceMetricData(mContext); - when(mTraceInfoMetricCollector.getFileSuffix()).thenReturn("1"); - - mTraceInfoMetricCollector.collect(mDevice, runData); - - // Verify that we logged the metric file. - verify(mListener).testLog(eq("trace-1"), eq(LogDataType.TEXT), any()); - } -} diff --git a/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java b/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java index dfb8b951c..65e7c0611 100644 --- a/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java +++ b/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java @@ -16,6 +16,8 @@ package com.android.tradefed.invoker; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -425,4 +427,26 @@ public class SandboxedInvocationExecutionTest { FileUtil.deleteFile(buildFile); } } + + @Test + public void testBuildInfo_testTag() throws Exception { + IBuildInfo info = new BuildInfo(); + assertEquals("stub", info.getTestTag()); + File testsDir = FileUtil.createTempDir("doesnt_matter_testsdir"); + try { + info.setFile(BuildInfoFileKey.TESTDIR_IMAGE, testsDir, "tests"); + mContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, info); + mConfig.getCommandOptions().setTestTag("test"); + TestInformation testInfo = + TestInformation.newBuilder().setInvocationContext(mContext).build(); + assertNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY)); + mExecution.fetchBuild(testInfo, mConfig, null, null); + // Build test tag was updated + assertEquals("test", info.getTestTag()); + // Execution file was back filled + assertNotNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY)); + } finally { + FileUtil.recursiveDelete(testsDir); + } + } } diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java index b66ba56b4..768ae2c5d 100644 --- a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java +++ b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java @@ -174,15 +174,17 @@ public class TestInvocationMultiTest { EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger); EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc); mMockLogger.init(); + EasyMock.expectLastCall().times(2); EasyMock.expect(mMockLogger.getLog()) .andReturn(new ByteArrayInputStreamSource("fake".getBytes())); mMockLogger.closeLog(); - EasyMock.expectLastCall().times(2); + EasyMock.expectLastCall().times(3); mMockLogRegistry.registerLogger(mMockLogger); + EasyMock.expectLastCall().times(2); mMockLogRegistry.dumpToGlobalLog(mMockLogger); mMockLogRegistry.unregisterLogger(); - EasyMock.expectLastCall().times(2); + EasyMock.expectLastCall().times(3); EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty"); EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions()); @@ -270,15 +272,17 @@ public class TestInvocationMultiTest { EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger); EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc); mMockLogger.init(); + EasyMock.expectLastCall().times(2); EasyMock.expect(mMockLogger.getLog()) .andReturn(new ByteArrayInputStreamSource("fake".getBytes())); mMockLogger.closeLog(); - EasyMock.expectLastCall().times(2); + EasyMock.expectLastCall().times(3); mMockLogRegistry.registerLogger(mMockLogger); + EasyMock.expectLastCall().times(2); mMockLogRegistry.dumpToGlobalLog(mMockLogger); mMockLogRegistry.unregisterLogger(); - EasyMock.expectLastCall().times(2); + EasyMock.expectLastCall().times(3); EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty"); EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions()); @@ -352,15 +356,17 @@ public class TestInvocationMultiTest { EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger); EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc); mMockLogger.init(); + EasyMock.expectLastCall().times(2); EasyMock.expect(mMockLogger.getLog()) .andReturn(new ByteArrayInputStreamSource("fake".getBytes())); mMockLogger.closeLog(); - EasyMock.expectLastCall().times(2); + EasyMock.expectLastCall().times(3); mMockLogRegistry.registerLogger(mMockLogger); + EasyMock.expectLastCall().times(2); mMockLogRegistry.dumpToGlobalLog(mMockLogger); mMockLogRegistry.unregisterLogger(); - EasyMock.expectLastCall().times(2); + EasyMock.expectLastCall().times(3); EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty"); EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions()); @@ -442,15 +448,17 @@ public class TestInvocationMultiTest { EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger); EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc); mMockLogger.init(); + EasyMock.expectLastCall().times(2); EasyMock.expect(mMockLogger.getLog()) .andReturn(new ByteArrayInputStreamSource("fake".getBytes())); mMockLogger.closeLog(); - EasyMock.expectLastCall().times(2); + EasyMock.expectLastCall().times(3); mMockLogRegistry.registerLogger(mMockLogger); + EasyMock.expectLastCall().times(2); mMockLogRegistry.dumpToGlobalLog(mMockLogger); mMockLogRegistry.unregisterLogger(); - EasyMock.expectLastCall().times(2); + EasyMock.expectLastCall().times(3); EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty"); EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions()); diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java index f8c4eb287..dc53f526b 100644 --- a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java +++ b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java @@ -412,11 +412,6 @@ public class TestInvocationTest { setupMockFailureListeners(exception); setupInvoke(); - EasyMock.reset(mMockLogger, mMockLogRegistry); - mMockLogRegistry.registerLogger(mMockLogger); - mMockLogger.init(); - mMockLogger.closeLog(); - mMockLogRegistry.unregisterLogger(); IRemoteTest test = EasyMock.createMock(IRemoteTest.class); CommandOptions cmdOptions = new CommandOptions(); final String expectedTestTag = "TEST_TAG"; @@ -449,13 +444,6 @@ public class TestInvocationTest { setupMockFailureListenersAny( new BuildRetrievalError("fake", InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR), true); - - EasyMock.reset(mMockLogger, mMockLogRegistry); - mMockLogRegistry.registerLogger(mMockLogger); - mMockLogger.init(); - mMockLogger.closeLog(); - mMockLogRegistry.unregisterLogger(); - EasyMock.expect(mMockLogger.getLog()).andReturn(EMPTY_STREAM_SOURCE); EasyMock.expect(mMockDevice.getLogcat()).andReturn(EMPTY_STREAM_SOURCE).times(2); mMockDevice.clearLogcat(); @@ -484,12 +472,6 @@ public class TestInvocationTest { "No build found to test.", InfraErrorIdentifier.ARTIFACT_NOT_FOUND), true); - EasyMock.reset(mMockLogger, mMockLogRegistry); - mMockLogRegistry.registerLogger(mMockLogger); - mMockLogger.init(); - mMockLogger.closeLog(); - mMockLogRegistry.unregisterLogger(); - EasyMock.expect(mMockLogger.getLog()).andReturn(EMPTY_STREAM_SOURCE); EasyMock.expect(mMockDevice.getLogcat()).andReturn(EMPTY_STREAM_SOURCE).times(2); mMockDevice.clearLogcat(); @@ -520,13 +502,6 @@ public class TestInvocationTest { "No build found to test.", InfraErrorIdentifier.ARTIFACT_NOT_FOUND), true, /* don't expect host log */ false); - - EasyMock.reset(mMockLogger, mMockLogRegistry); - mMockLogRegistry.registerLogger(mMockLogger); - mMockLogger.init(); - mMockLogger.closeLog(); - EasyMock.expectLastCall().times(2); - IRemoteTest test = EasyMock.createMock(IRemoteTest.class); mStubConfiguration.setTest(test); // Host log fails to report @@ -537,7 +512,7 @@ public class TestInvocationTest { Capture<IBuildInfo> captured = new Capture<>(); mMockBuildProvider.cleanUp(EasyMock.capture(captured)); mMockLogRegistry.unregisterLogger(); - EasyMock.expectLastCall().times(2); + mMockLogger.closeLog(); mMockLogRegistry.dumpToGlobalLog(mMockLogger); replayMocks(test, mockRescheduler); mTestInvocation.invoke(mStubInvocationMetadata, mStubConfiguration, mockRescheduler); @@ -1149,9 +1124,12 @@ public class TestInvocationTest { mMockTestListener.invocationFailed(EasyMock.<FailureDescription>anyObject()); mMockSummaryListener.invocationFailed(EasyMock.<FailureDescription>anyObject()); } else { + FailureStatus failureStatus = FailureStatus.INFRA_FAILURE; + if (throwable instanceof BuildError) { + failureStatus = FailureStatus.DEPENDENCY_ISSUE; + } FailureDescription failure = - FailureDescription.create( - throwable.getMessage(), FailureStatus.INFRA_FAILURE) + FailureDescription.create(throwable.getMessage(), failureStatus) .setCause(throwable); if (throwable instanceof BuildRetrievalError) { failure.setActionInProgress(ActionInProgress.FETCHING_ARTIFACTS); diff --git a/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java b/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java index 7ce1e0de3..d7a9b683a 100644 --- a/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java +++ b/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java @@ -89,7 +89,7 @@ public final class InvocationLocalTest { Object value0 = invocation(() -> local.get()); Object value1 = invocation(() -> local.get()); - assertThat(value0).isNotSameAs(value1); + assertThat(value0).isNotSameInstanceAs(value1); } /** diff --git a/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java b/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java index 91b8e7198..47a791f2b 100644 --- a/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java +++ b/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.android.tradefed.build.BuildInfo; import com.android.tradefed.build.StubBuildProvider; @@ -47,6 +48,7 @@ import com.android.tradefed.testtype.StubTest; import com.android.tradefed.testtype.suite.ITestSuite; import com.android.tradefed.util.FileUtil; +import java.util.Arrays; import org.easymock.EasyMock; import org.junit.Assert; import org.junit.Before; @@ -256,11 +258,36 @@ public class StrictShardHelperTest { } } + public class FakeStrictShardHelper extends StrictShardHelper { + List<IRemoteTest> fakeModules = new ArrayList<>(); + + public FakeStrictShardHelper(List<IRemoteTest> modules) { + fakeModules.addAll(modules); + } + + @Override + protected List<List<IRemoteTest>> splitTests(List<IRemoteTest> fullList, int shardCount) { + List<List<IRemoteTest>> shards = new ArrayList<>(); + shards.add(new ArrayList<>(fakeModules)); + shards.add(new ArrayList<>(fakeModules)); + return shards; + } + } + private ITestSuite createFakeSuite(String name) throws Exception { ITestSuite suite = new SplitITestSuite(name); return suite; } + private ITestSuite createFakeSuite(String name, boolean intraModuleSharding) throws Exception { + ITestSuite suite = new SplitITestSuite(name); + if (!intraModuleSharding) { + OptionSetter setter = new OptionSetter(suite); + setter.setOptionValue("intra-module-sharding", "false"); + } + return suite; + } + private List<IRemoteTest> testShard(int shardIndex) throws Exception { mContext.addAllocatedDevice("default", EasyMock.createMock(ITestDevice.class)); List<IRemoteTest> test = new ArrayList<>(); @@ -282,6 +309,26 @@ public class StrictShardHelperTest { return mConfig.getTests(); } + private List<IRemoteTest> createITestSuiteList(List<String> modules) throws Exception { + List<IRemoteTest> tests = new ArrayList<>(); + for (String name : modules) { + tests.add(createFakeSuite(name, false).split(2, mTestInfo).iterator().next()); + } + + CommandOptions options = new CommandOptions(); + OptionSetter setter = new OptionSetter(options); + setter.setOptionValue("shard-count", "2"); + setter.setOptionValue("shard-index", Integer.toString(1)); + setter.setOptionValue("optimize-mainline-test", "true"); + mConfig.setCommandOptions(options); + mConfig.setCommandLine(new String[] {"empty"}); + mConfig.setTests(tests); + + FakeStrictShardHelper fakeHelper = new FakeStrictShardHelper(tests); + fakeHelper.shardConfig(mConfig, mTestInfo, mRescheduler, null); + return mConfig.getTests(); + } + /** * Total for all the _shardX test should be 14 tests (2 per modules). 6 for module1: 3 module1 * shard * 2 4 for module2: 2 module2 shard * 2 4 for module3: 2 module3 shard * 2 @@ -304,6 +351,61 @@ public class StrictShardHelperTest { assertEquals(1, ((ITestSuite) res.get(2)).getDirectModule().numTests()); } + /** + * Test that the unsorted test modules are re-ordered. + */ + @Test + public void testReorderTestModules() throws Exception { + List<String> unSortedModules = + Arrays.asList( + "module1[com.android.mod1.apex]", + "module1[com.android.mod1.apex+com.android.mod2.apex]", + "module2[com.android.mod1.apex]", + "module1[com.android.mod3.apk]", + "module2[com.android.mod1.apex+com.android.mod2.apex]", + "module2[com.android.mod3.apk]", + "module3[com.android.mod1.apex+com.android.mod2.apex]", + "module3[com.android.mod3.apk]", + "module4[com.android.mod3.apk]", + "module5[com.android.mod3.apk]" + ); + List<IRemoteTest> res = createITestSuiteList(unSortedModules); + + List<String> sortedModules = + Arrays.asList( + "module1[com.android.mod1.apex]", + "module2[com.android.mod1.apex]", + "module1[com.android.mod1.apex+com.android.mod2.apex]", + "module2[com.android.mod1.apex+com.android.mod2.apex]", + "module3[com.android.mod1.apex+com.android.mod2.apex]", + "module1[com.android.mod3.apk]", + "module2[com.android.mod3.apk]", + "module3[com.android.mod3.apk]", + "module4[com.android.mod3.apk]", + "module5[com.android.mod3.apk]" + ); + for (int i = 0 ; i < sortedModules.size() ; i++) { + assertEquals(sortedModules.get(i), ((ITestSuite)res.get(i)).getDirectModule().getId()); + } + } + + /** + * Test that the there exist a module with invalid parameterized modules defined. + */ + @Test + public void testReorderTestModulesWithUnexpectedMainlineModules() throws Exception { + List<String> modules = Arrays.asList("module1[com.mod1.apex]", "module1[com.mod1]"); + try { + List<IRemoteTest> res = createITestSuiteList(modules); + fail("Should have thrown an exception."); + } catch (RuntimeException expected) { + // expected + assertTrue(expected.getMessage().contains( + "Module: module1[com.mod1] doesn't match the pattern for mainline " + + "modules. The pattern should end with apk/apex/apks.")); + } + } + @Test public void testMergeSuite_shard1() throws Exception { List<IRemoteTest> res = testShard(1); diff --git a/tests/src/com/android/tradefed/monitoring/LabResourceDeviceMonitorTest.java b/tests/src/com/android/tradefed/monitoring/LabResourceDeviceMonitorTest.java new file mode 100644 index 000000000..ee51dfdfe --- /dev/null +++ b/tests/src/com/android/tradefed/monitoring/LabResourceDeviceMonitorTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.monitoring; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class LabResourceDeviceMonitorTest { + + private LabResourceDeviceMonitor mLabResourceDeviceMonitor; + + @Before + public void setUp() { + mLabResourceDeviceMonitor = new LabResourceDeviceMonitor(); + } + + @Test + public void testServerStartAndShutdown() { + Assert.assertFalse( + "server should be empty before monitor run", + mLabResourceDeviceMonitor.getServer().isPresent()); + mLabResourceDeviceMonitor.run(); + Assert.assertTrue( + "server should present after monitor run", + mLabResourceDeviceMonitor.getServer().isPresent()); + Assert.assertEquals( + LabResourceDeviceMonitor.DEFAULT_PORT, + mLabResourceDeviceMonitor.getServer().get().getPort()); + mLabResourceDeviceMonitor.stop(); + Assert.assertTrue( + "server should be shutdown after monitor stop", + mLabResourceDeviceMonitor.getServer().get().isShutdown()); + } +} diff --git a/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java b/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java index d83c34319..82fc304bf 100644 --- a/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java +++ b/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java @@ -306,10 +306,11 @@ public class PerfettoGenericPostProcessorTest { } /** - * Test metrics enabled with key prefixing. + * Test metrics enabled with key and string value prefixing. */ @Test - public void testParsingWithKeyPrefixing() throws ConfigurationException, IOException { + public void testParsingWithKeyAndStringValuePrefixing() + throws ConfigurationException, IOException { setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true); mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE); mOptionSetter.setOptionValue(KEY_PREFIX_OPTION, @@ -320,8 +321,8 @@ public class PerfettoGenericPostProcessorTest { PREFIX_OPTION_VALUE, new LogFile( perfettoMetricProtoFile.getAbsolutePath(), "some.url", LogDataType.TEXTPB)); - Map<String, Metric.Builder> parsedMetrics = - mProcessor.processRunMetricsAndLogs(new HashMap<>(), testLogs); + Map<String, Metric.Builder> parsedMetrics = mProcessor + .processRunMetricsAndLogs(new HashMap<>(), testLogs); assertMetricsContain(parsedMetrics, "perfetto_android_hwui_metric-process_info-process_name-com.android.systemui-all_mem_min", @@ -329,6 +330,28 @@ public class PerfettoGenericPostProcessorTest { } + /** + * Test metrics enabled with key and integer value prefixing. + */ + @Test + public void testParsingWithKeyAndIntegerValuePrefixing() + throws ConfigurationException, IOException { + setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true); + mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE); + mOptionSetter.setOptionValue(KEY_PREFIX_OPTION, + "perfetto.protos.AndroidCpuMetric.CoreData.id"); + mOptionSetter.setOptionValue(ALL_METRICS_OPTION, "true"); + Map<String, LogFile> testLogs = new HashMap<>(); + testLogs.put( + PREFIX_OPTION_VALUE, + new LogFile( + perfettoMetricProtoFile.getAbsolutePath(), "some.url", LogDataType.TEXTPB)); + Map<String, Metric.Builder> parsedMetrics = mProcessor + .processRunMetricsAndLogs(new HashMap<>(), testLogs); + assertMetricsContain(parsedMetrics, "perfetto_android_cpu-process_info-name-com.google." + + "android.apps.messaging-threads-name-BG Thread #1-core-id-1-metrics-runtime_ns", + 14376405); + } /** Test the post processor can parse binary perfetto metric proto format. */ @Test @@ -515,6 +538,54 @@ public class PerfettoGenericPostProcessorTest { " all_mem_min: 15120269\n" + " all_mem_avg: 24468104.289592762\n" + " }\n" + + "}" + + "android_cpu {\n" + + " process_info {\n" + + " name: \"com.google.android.apps.messaging\"\n" + + " metrics {\n" + + " mcycles: 139\n" + + " runtime_ns: 639064902\n" + + " min_freq_khz: 576000\n" + + " max_freq_khz: 2016000\n" + + " avg_freq_khz: 324000\n" + + " }\n" + + " threads {\n" + + " name: \"BG Thread #1\"\n" + + " core {\n" + + " id: 0\n" + + " metrics {\n" + + " runtime_ns: 8371202\n" + + " }\n" + + " }\n" + + " core {\n" + + " id: 1\n" + + " metrics {\n" + + " mcycles: 0\n" + + " runtime_ns: 14376405\n" + + " min_freq_khz: 1785600\n" + + " max_freq_khz: 1785600\n" + + " avg_freq_khz: 57977\n" + + " }\n" + + " }\n" + + " metrics {\n" + + " mcycles: 0\n" + + " runtime_ns: 22747607\n" + + " min_freq_khz: 1785600\n" + + " max_freq_khz: 1785600\n" + + " avg_freq_khz: 36000\n" + + " }\n" + + " core_type {\n" + + " type: \"little\"\n" + + " metrics {\n" + + " mcycles: 0\n" + + " runtime_ns: 22747607\n" + + " min_freq_khz: 1785600\n" + + " max_freq_khz: 1785600\n" + + " avg_freq_khz: 36000\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + "}"; FileWriter fileWriter = null; try { @@ -593,3 +664,4 @@ public class PerfettoGenericPostProcessorTest { .getSingleString()))); } } + diff --git a/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java b/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java index ff0692e9e..852a4df54 100644 --- a/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java +++ b/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java @@ -80,6 +80,8 @@ public class GeneralTestsConfigValidation implements IBuildReceiver { "com.android.tradefed.testtype.rust.RustBinaryTest", "com.android.tradefed.testtype.StubTest", "com.android.tradefed.testtype.ArtRunTest", + "com.android.tradefed.testtype.ArtGTest", + "com.android.tradefed.testtype.mobly.MoblyBinaryHostTest", // Others "com.google.android.deviceconfig.RebootTest")); diff --git a/tests/src/com/android/tradefed/result/JsonHttpTestResultReporterTest.java b/tests/src/com/android/tradefed/result/JsonHttpTestResultReporterTest.java index d9b8eb6ba..26e020b31 100644 --- a/tests/src/com/android/tradefed/result/JsonHttpTestResultReporterTest.java +++ b/tests/src/com/android/tradefed/result/JsonHttpTestResultReporterTest.java @@ -25,6 +25,7 @@ import com.android.tradefed.config.ConfigurationException; import com.android.tradefed.config.OptionSetter; import com.android.tradefed.invoker.IInvocationContext; import com.android.tradefed.invoker.InvocationContext; +import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.util.proto.TfMetricProtoUtil; import org.json.JSONException; @@ -65,14 +66,18 @@ public class JsonHttpTestResultReporterTest { @Test public void testSkipFailedRuns_notSet() throws JSONException { mReporter.invocationStarted(mContext); - injectTestRun(mReporter, "run1", "test", "metric1", 0, true); - injectTestRun(mReporter, "run2", "test", "metric2", 1, false); + injectTestRun(mReporter, "run1", "test", "123", 0, true); + injectTestRun(mReporter, "run2", "test", "456", 1, false); mReporter.invocationEnded(0); ArgumentCaptor<JSONObject> jsonCaptor = ArgumentCaptor.forClass(JSONObject.class); verify(mReporter).postResults(jsonCaptor.capture()); // Both runs should be in the posted metrics. Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).has("run1")); + Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).getJSONObject("run1") + .has("run_metric")); Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).has("run2")); + Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).getJSONObject("run2") + .has("run_metric")); } /** Test that failed runs are skipped when skip-failed-runs is set. */ @@ -81,16 +86,59 @@ public class JsonHttpTestResultReporterTest { OptionSetter optionSetter = new OptionSetter(mReporter); optionSetter.setOptionValue(SKIP_FAILED_RUNS_OPTION, String.valueOf(true)); mReporter.invocationStarted(mContext); - injectTestRun(mReporter, "run1", "test", "metric1", 0, true); - injectTestRun(mReporter, "run2", "test", "metric2", 1, false); + injectTestRun(mReporter, "run1", "test", "123", 0, true); + injectTestRun(mReporter, "run2", "test", "456", 1, false); mReporter.invocationEnded(0); ArgumentCaptor<JSONObject> jsonCaptor = ArgumentCaptor.forClass(JSONObject.class); verify(mReporter).postResults(jsonCaptor.capture()); // Only the first run should be in the posted metrics. Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).has("run1")); + Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).getJSONObject("run1") + .has("run_metric")); Assert.assertFalse(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).has("run2")); } + /** Test non-numeric metrics are not posted in the final JSONObject. */ + @Test + public void testInvalidMetricsNotSet() throws ConfigurationException, JSONException { + OptionSetter optionSetter = new OptionSetter(mReporter); + optionSetter.setOptionValue(SKIP_FAILED_RUNS_OPTION, String.valueOf(true)); + mReporter.invocationStarted(mContext); + // Inject invalid metric "1.23invalid". + injectTestRun(mReporter, "run1", "test", "1.23invalid", 0, false); + mReporter.invocationEnded(0); + ArgumentCaptor<JSONObject> jsonCaptor = ArgumentCaptor.forClass(JSONObject.class); + verify(mReporter).postResults(jsonCaptor.capture()); + // Only the first run should be in the posted metrics. + CLog.i(jsonCaptor.getValue().toString()); + // Check the metric is not added in the JSONObject. + Assert.assertFalse(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY) + .getJSONObject("run1").has("run_metric")); + } + + /** Test valid and invalid metrics in JSONObject. */ + @Test + public void testInvalidAndInvalidMetricsNotSet() throws ConfigurationException, JSONException { + OptionSetter optionSetter = new OptionSetter(mReporter); + optionSetter.setOptionValue(SKIP_FAILED_RUNS_OPTION, String.valueOf(true)); + mReporter.invocationStarted(mContext); + // Inject invalid metric "1.23invalid". + injectTestRun(mReporter, "run1", "test1", "1.23invalid", 0, false); + // Inject valid metric "5.99". + injectTestRun(mReporter, "run2", "test1", "5.99", 0, false); + mReporter.invocationEnded(0); + ArgumentCaptor<JSONObject> jsonCaptor = ArgumentCaptor.forClass(JSONObject.class); + verify(mReporter).postResults(jsonCaptor.capture()); + CLog.i(jsonCaptor.getValue().toString()); + // Check the invalid metric is not added in the JSONObject. + Assert.assertFalse(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY) + .getJSONObject("run1").has("run_metric")); + // Check the valid metric is added in the JSONObject. + Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY) + .getJSONObject("run2").has("run_metric")); + } + + /** Test for parsing additional device details when collect device details is enabled. */ @Test public void testIncludeAdditionalTestDetails() throws ConfigurationException { @@ -132,4 +180,4 @@ public class JsonHttpTestResultReporterTest { target.testRunEnded(0, TfMetricProtoUtil.upgradeConvert(runMetrics)); return test; } -} +}
\ No newline at end of file diff --git a/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java b/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java index 1bf8eff76..583a4c04d 100644 --- a/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java +++ b/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java @@ -216,4 +216,56 @@ public class LogcatCrashResultForwarderTest { + "\tat class.method1(Class.java:1)\n" + "\tat class.method2(Class.java:2)\n")); } + + /** Test that test-timeout tests have failure status TIMED_OUT. */ + @Test + @SuppressWarnings("MustBeClosedChecker") + public void testTestTimedOutTests() { + String trace = + "org.junit.runners.model.TestTimedOutException: " + + "test timed out after 1000 milliseconds"; + mReporter = new LogcatCrashResultForwarder(mMockDevice, mMockListener); + TestDescription test = new TestDescription("com.class", "test"); + + mMockListener.testStarted(test, 0L); + + Capture<FailureDescription> captured = new Capture<>(); + mMockListener.testFailed(EasyMock.eq(test), EasyMock.capture(captured)); + mMockListener.testEnded(test, 5L, new HashMap<String, Metric>()); + + EasyMock.replay(mMockListener, mMockDevice); + mReporter.testStarted(test, 0L); + mReporter.testFailed(test, trace); + mReporter.testEnded(test, 5L, new HashMap<String, Metric>()); + EasyMock.verify(mMockListener, mMockDevice); + assertTrue(captured.getValue().getErrorMessage().contains(trace)); + assertTrue(FailureStatus.TIMED_OUT.equals(captured.getValue().getFailureStatus())); + } + + /** Test that shell-timeout tests have failure status TIMED_OUT. */ + @Test + @SuppressWarnings("MustBeClosedChecker") + public void testShellTimedOutTests() { + String trace = + "Test failed to run to completion. " + + " Reason: 'Failed to receive adb shell test output within 3000 ms. " + + "Test may have timed out, or adb connection to device became " + + "unresponsive'. Check device logcat for details"; + mReporter = new LogcatCrashResultForwarder(mMockDevice, mMockListener); + TestDescription test = new TestDescription("com.class", "test"); + + mMockListener.testStarted(test, 0L); + + Capture<FailureDescription> captured = new Capture<>(); + mMockListener.testFailed(EasyMock.eq(test), EasyMock.capture(captured)); + mMockListener.testEnded(test, 5L, new HashMap<String, Metric>()); + + EasyMock.replay(mMockListener, mMockDevice); + mReporter.testStarted(test, 0L); + mReporter.testFailed(test, trace); + mReporter.testEnded(test, 5L, new HashMap<String, Metric>()); + EasyMock.verify(mMockListener, mMockDevice); + assertTrue(captured.getValue().getErrorMessage().contains(trace)); + assertTrue(FailureStatus.TIMED_OUT.equals(captured.getValue().getFailureStatus())); + } } diff --git a/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java b/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java index 63556ab23..b7cc8eb80 100644 --- a/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java +++ b/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java @@ -37,6 +37,7 @@ public class ErrorIdentifierTest { List<ErrorIdentifier> errors = new ArrayList<>(); errors.addAll(Arrays.asList(InfraErrorIdentifier.values())); errors.addAll(Arrays.asList(DeviceErrorIdentifier.values())); + errors.addAll(Arrays.asList(TestErrorIdentifier.values())); List<String> names = errors.stream().map(e -> e.name()).collect(Collectors.toList()); Set<String> uniques = new HashSet<>(); diff --git a/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java b/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java index 43aaa7a13..bd17c8777 100644 --- a/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java +++ b/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java @@ -211,6 +211,135 @@ public class ResultAggregatorTest { } @Test + public void testForwarding_assumptionFailure() { + mDetailedListener = EasyMock.createStrictMock(ITestDetailedReceiver.class); + LogFile test1Log = new LogFile("test1", "url", LogDataType.TEXT); + LogFile test2LogBefore = new LogFile("test2-before", "url", LogDataType.TEXT); + LogFile test2LogAfter = new LogFile("test2-after", "url", LogDataType.TEXT); + LogFile testRun1LogBefore = new LogFile("test-run1-before", "url", LogDataType.TEXT); + LogFile testRun1LogAfter = new LogFile("test-run1-after", "url", LogDataType.TEXT); + LogFile beforeEnd = new LogFile("path", "url", LogDataType.TEXT); + LogFile betweenAttemptsLog = new LogFile("between-attempts", "url", LogDataType.TEXT); + LogFile moduleLog = new LogFile("module-log", "url", LogDataType.TEXT); + TestDescription test1 = new TestDescription("classname", "test1"); + TestDescription test2 = new TestDescription("classname", "test2"); + ILogSaver logger = EasyMock.createMock(ILogSaver.class); + + EasyMock.expect(mDetailedListener.supportGranularResults()).andStubReturn(true); + + // Invocation level + mAggListener.setLogSaver(logger); + mAggListener.invocationStarted(mInvocationContext); + EasyMock.expect(mAggListener.getSummary()).andStubReturn(null); + mDetailedListener.setLogSaver(logger); + mDetailedListener.invocationStarted(mInvocationContext); + EasyMock.expect(mDetailedListener.getSummary()).andStubReturn(null); + + mAggListener.testModuleStarted(mModuleContext); + mDetailedListener.testModuleStarted(mModuleContext); + + // Detailed receives the breakdown + mDetailedListener.testRunStarted( + EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong()); + mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong()); + mDetailedListener.logAssociation("test1-log", test1Log); + mDetailedListener.testEnded( + EasyMock.eq(test1), + EasyMock.anyLong(), + EasyMock.<HashMap<String, Metric>>anyObject()); + mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong()); + mDetailedListener.logAssociation("test2-before-log", test2LogBefore); + mDetailedListener.testFailed(test2, FailureDescription.create("I failed. retry me.")); + mDetailedListener.logAssociation("test2-after-log", test2LogAfter); + mDetailedListener.testEnded( + EasyMock.eq(test2), + EasyMock.anyLong(), + EasyMock.<HashMap<String, Metric>>anyObject()); + mDetailedListener.logAssociation("test-run1-before-log", testRun1LogBefore); + mDetailedListener.logAssociation("test-run1-after-log", testRun1LogAfter); + mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>()); + mDetailedListener.testRunStarted( + EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(1), EasyMock.anyLong()); + mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong()); + mDetailedListener.testAssumptionFailure( + EasyMock.eq(test2), EasyMock.eq(FailureDescription.create("Assump failure"))); + mDetailedListener.testEnded( + EasyMock.eq(test2), + EasyMock.anyLong(), + EasyMock.<HashMap<String, Metric>>anyObject()); + mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>()); + mDetailedListener.logAssociation("between-attempts", betweenAttemptsLog); + mDetailedListener.logAssociation("module-log", moduleLog); + + // Aggregated listeners receives the aggregated results + mAggListener.testRunStarted( + EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong()); + mAggListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong()); + mAggListener.logAssociation("test1-log", test1Log); + mAggListener.testEnded( + EasyMock.eq(test1), + EasyMock.anyLong(), + EasyMock.<HashMap<String, Metric>>anyObject()); + mAggListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong()); + mAggListener.testAssumptionFailure( + EasyMock.eq(test2), (FailureDescription) EasyMock.anyObject()); + mAggListener.logAssociation("test2-before-log", test2LogBefore); + mAggListener.logAssociation("test2-after-log", test2LogAfter); + mAggListener.testEnded( + EasyMock.eq(test2), + EasyMock.anyLong(), + EasyMock.<HashMap<String, Metric>>anyObject()); + mAggListener.logAssociation("test-run1-before-log", testRun1LogBefore); + mAggListener.logAssociation("test-run1-after-log", testRun1LogAfter); + mAggListener.testRunEnded(450L, new HashMap<String, Metric>()); + mAggListener.logAssociation("between-attempts", betweenAttemptsLog); + mAggListener.logAssociation("module-log", moduleLog); + mAggListener.testModuleEnded(); + mDetailedListener.testModuleEnded(); + mAggListener.logAssociation("before-end", beforeEnd); + mAggListener.invocationEnded(500L); + mDetailedListener.logAssociation("before-end", beforeEnd); + mDetailedListener.invocationEnded(500L); + + EasyMock.replay(mAggListener, mDetailedListener); + mAggregator = + new TestableResultAggregator( + Arrays.asList(mAggListener, mDetailedListener), + RetryStrategy.RETRY_ANY_FAILURE); + mAggregator.setLogSaver(logger); + mAggregator.invocationStarted(mInvocationContext); + mAggregator.testModuleStarted(mModuleContext); + // Attempt 1 + mAggregator.testRunStarted("run1", 2, 0); + mAggregator.testStarted(test1); + mAggregator.logAssociation("test1-log", test1Log); + mAggregator.testEnded(test1, new HashMap<String, Metric>()); + mAggregator.testStarted(test2); + mAggregator.logAssociation("test2-before-log", test2LogBefore); + mAggregator.testFailed(test2, FailureDescription.create("I failed. retry me.")); + mAggregator.logAssociation("test2-after-log", test2LogAfter); + mAggregator.testEnded(test2, new HashMap<String, Metric>()); + mAggregator.logAssociation("test-run1-before-log", testRun1LogBefore); + mAggregator.testRunFailed("run fail"); + mAggregator.logAssociation("test-run1-after-log", testRun1LogAfter); + mAggregator.testRunEnded(450L, new HashMap<String, Metric>()); + mAggregator.logAssociation("between-attempts", betweenAttemptsLog); + // Attempt 2 + mAggregator.testRunStarted("run1", 2, 1); + mAggregator.testStarted(test2); + mAggregator.testAssumptionFailure(test2, FailureDescription.create("Assump failure")); + mAggregator.testEnded(test2, new HashMap<String, Metric>()); + mAggregator.testRunEnded(450L, new HashMap<String, Metric>()); + + mAggregator.logAssociation("module-log", moduleLog); + mAggregator.testModuleEnded(); + mAggregator.logAssociation("before-end", beforeEnd); + mAggregator.invocationEnded(500L); + EasyMock.verify(mAggListener, mDetailedListener); + assertEquals("run fail", mAggregator.getInvocationMetricRunError()); + } + + @Test public void testForwarding_runFailure() { mDetailedListener = EasyMock.createStrictMock(ITestDetailedReceiver.class); TestDescription test1 = new TestDescription("classname", "test1"); diff --git a/tests/src/com/android/tradefed/sandbox/SandboxedInvocationExecutionTest.java b/tests/src/com/android/tradefed/sandbox/SandboxedInvocationExecutionTest.java deleted file mode 100644 index 7594ce45c..000000000 --- a/tests/src/com/android/tradefed/sandbox/SandboxedInvocationExecutionTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tradefed.sandbox; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import com.android.tradefed.build.BuildInfo; -import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey; -import com.android.tradefed.build.IBuildInfo; -import com.android.tradefed.config.Configuration; -import com.android.tradefed.config.ConfigurationDef; -import com.android.tradefed.config.IConfiguration; -import com.android.tradefed.invoker.ExecutionFiles.FilesKey; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.invoker.InvocationContext; -import com.android.tradefed.invoker.TestInformation; -import com.android.tradefed.invoker.sandbox.SandboxedInvocationExecution; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.io.File; - -/** Unit tests for {@link SandboxedInvocationExecution}. */ -@RunWith(JUnit4.class) -public class SandboxedInvocationExecutionTest { - - private SandboxedInvocationExecution mExecution; - private IInvocationContext mContext; - private IConfiguration mConfig; - - @Before - public void setUp() { - mExecution = new SandboxedInvocationExecution(); - mContext = new InvocationContext(); - mConfig = new Configuration("name", "desc"); - mConfig.getConfigurationDescription().setSandboxed(true); - } - - @Test - public void testBuildInfo_testTag() throws Exception { - IBuildInfo info = new BuildInfo(); - assertEquals("stub", info.getTestTag()); - info.setFile(BuildInfoFileKey.TESTDIR_IMAGE, new File("doesnt_matter_testsdir"), "tests"); - mContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, info); - mConfig.getCommandOptions().setTestTag("test"); - TestInformation testInfo = - TestInformation.newBuilder().setInvocationContext(mContext).build(); - assertNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY)); - mExecution.fetchBuild(testInfo, mConfig, null, null); - // Build test tag was updated - assertEquals("test", info.getTestTag()); - // Execution file was back filled - assertNotNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY)); - } -} diff --git a/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java b/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java index a32f9b646..244934cf5 100644 --- a/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java +++ b/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java @@ -266,6 +266,22 @@ public class DeviceSetupTest extends TestCase { EasyMock.verify(mMockDevice); } + public void testSetup_wifi_network_empty() throws Exception { + doSetupExpectations(); + doCheckExternalStoreSpaceExpectations(); + doSettingExpectations("global", "wifi_on", "1"); + doCommandsExpectations("svc wifi enable"); + EasyMock.replay(mMockDevice); + + mDeviceSetup.setWifiNetwork(""); + mDeviceSetup.setWifiPsk("psk"); + + mDeviceSetup.setWifi(BinaryState.ON); + mDeviceSetup.setUp(mTestInfo); + + EasyMock.verify(mMockDevice); + } + public void testSetup_wifi_multiple_network_names() throws Exception { doSetupExpectations(); doCheckExternalStoreSpaceExpectations(); diff --git a/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java b/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java index 9b38ce69b..e889441d2 100644 --- a/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java +++ b/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java @@ -28,10 +28,9 @@ import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; import com.android.tradefed.util.FileUtil; import com.android.tradefed.util.ZipUtil; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; + import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +39,10 @@ import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + /** Unit tests for {@link DynamicSystemPreparer}. */ @RunWith(JUnit4.class) public class DynamicSystemPreparerTest { @@ -95,15 +98,12 @@ public class DynamicSystemPreparerTest { } } - @Test - public void testSetUp() throws TargetSetupError, BuildError, DeviceNotAvailableException { - Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz"))) - .thenReturn(Boolean.TRUE); + private void mockGsiToolStatus(String status) throws DeviceNotAvailableException { doAnswer( new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) { - byte[] outputBytes = "running".getBytes(); + byte[] outputBytes = status.getBytes(); ((CollectingOutputReceiver) invocation.getArguments()[1]) .addOutput(outputBytes, 0, outputBytes.length); return null; @@ -112,10 +112,62 @@ public class DynamicSystemPreparerTest { .when(mMockDevice) .executeShellCommand( matches("gsi_tool status"), any(CollectingOutputReceiver.class)); + } + + @Test + public void testSetUp() throws TargetSetupError, BuildError, DeviceNotAvailableException { + Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz"))) + .thenReturn(Boolean.TRUE); + Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(true); + mockGsiToolStatus("running"); CommandResult res = new CommandResult(); res.setStdout(""); res.setStatus(CommandStatus.SUCCESS); Mockito.when(mMockDevice.executeShellV2Command("gsi_tool enable")).thenReturn(res); mPreparer.setUp(mMockDevice, mBuildInfo); } + + @Test + public void testSetUp_installationFail() throws BuildError, DeviceNotAvailableException { + Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz"))) + .thenReturn(Boolean.TRUE); + Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(false); + try { + mPreparer.setUp(mMockDevice, mBuildInfo); + Assert.fail("setUp() should have thrown."); + } catch (TargetSetupError e) { + Assert.assertEquals( + "Timed out waiting for DSU installation to complete and reboot", + e.getMessage()); + } + } + + @Test + public void testSetUp_rebootFail() throws BuildError, DeviceNotAvailableException { + Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz"))) + .thenReturn(Boolean.TRUE); + Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(true); + Mockito.doThrow(new DeviceNotAvailableException()).when(mMockDevice).waitForDeviceOnline(); + try { + mPreparer.setUp(mMockDevice, mBuildInfo); + Assert.fail("setUp() should have thrown."); + } catch (TargetSetupError e) { + Assert.assertEquals("Timed out booting into DSU", e.getMessage()); + } + } + + @Test + public void testSetUp_noDsuRunningAfterRebootFail() + throws BuildError, DeviceNotAvailableException { + Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz"))) + .thenReturn(Boolean.TRUE); + Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(true); + mockGsiToolStatus("normal"); + try { + mPreparer.setUp(mMockDevice, mBuildInfo); + Assert.fail("setUp() should have thrown."); + } catch (TargetSetupError e) { + Assert.assertEquals("Failed to boot into DSU", e.getMessage()); + } + } } diff --git a/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java b/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java index f91253e95..bccf68d0e 100644 --- a/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java +++ b/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java @@ -178,21 +178,36 @@ public class GsiDeviceFlashPreparerTest { EasyMock.verify(mMockDevice, mMockRunUtil); } - /* Verifies that setUp will throw exception when there is no vbmeta.img in the zip file*/ + /* Verifies that setUp can pass when there is no vbmeta.img is provided*/ @Test - public void testSetUp_NoVbmetaImageInGsiZip() throws Exception { + public void testSetUp_Success_NoVbmetaImage() throws Exception { File gsiDir = FileUtil.createTempDir("gsi_folder", mTmpDir); File systemImg = new File(gsiDir, "system.img"); - File gsiZip = FileUtil.createTempFile("gsi_image", ".zip", mTmpDir); - ZipUtil.createZip(List.of(systemImg), gsiZip); - mBuildInfo.setFile("gsi_system.img", gsiZip, "0"); + FileUtil.writeToFile("ddd", systemImg); + mBuildInfo.setFile("gsi_system.img", systemImg, "0"); + mMockDevice.waitForDeviceOnline(); + EasyMock.expect(mMockDevice.getApiLevel()).andReturn(29); + mMockDevice.rebootIntoBootloader(); + mMockRunUtil.allowInterrupt(false); + mMockDevice.rebootIntoFastbootd(); + doGetSlotExpectation(); + EasyMock.expect( + mMockDevice.executeLongFastbootCommand( + "delete-logical-partition", "product_a")) + .andReturn(mSuccessResult); + EasyMock.expect(mMockDevice.executeLongFastbootCommand("erase", "system_a")) + .andReturn(mSuccessResult); + EasyMock.expect( + mMockDevice.executeLongFastbootCommand( + "flash", + "system", + mBuildInfo.getFile("gsi_system.img").getAbsolutePath())) + .andReturn(mSuccessResult); + EasyMock.expect(mMockDevice.executeLongFastbootCommand("-w")).andReturn(mSuccessResult); + mMockRunUtil.allowInterrupt(true); + doSetupExpectations(); EasyMock.replay(mMockDevice, mMockRunUtil); - try { - mPreparer.setUp(mTestInfo); - fail("TargetSetupError is expected"); - } catch (TargetSetupError e) { - // expected - } + mPreparer.setUp(mTestInfo); EasyMock.verify(mMockDevice, mMockRunUtil); } diff --git a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java index 2541183c3..0652a2106 100644 --- a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java +++ b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.when; import com.android.tradefed.build.IBuildInfo; import com.android.tradefed.command.remote.DeviceDescriptor; import com.android.tradefed.config.OptionSetter; +import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.device.ITestDevice.ApexInfo; import com.android.tradefed.invoker.IInvocationContext; @@ -35,6 +36,7 @@ import com.android.tradefed.util.FileUtil; import com.google.common.collect.ImmutableSet; +import java.util.Arrays; import org.easymock.EasyMock; import org.junit.After; import org.junit.Before; @@ -60,6 +62,8 @@ public class InstallApexModuleTargetPreparerTest { private TestInformation mTestInfo; private BundletoolUtil mMockBundletoolUtil; private File mFakeApex; + private File mFakeApex2; + private File mFakeApex3; private File mFakeApk; private File mFakeApk2; private File mFakePersistentApk; @@ -68,6 +72,8 @@ public class InstallApexModuleTargetPreparerTest { private File mBundletoolJar; private OptionSetter mSetter; private static final String APEX_PACKAGE_NAME = "com.android.FAKE_APEX_PACKAGE_NAME"; + private static final String APEX2_PACKAGE_NAME = "com.android.FAKE_APEX2_PACKAGE_NAME"; + private static final String APEX3_PACKAGE_NAME = "com.android.FAKE_APEX3_PACKAGE_NAME"; private static final String APK_PACKAGE_NAME = "com.android.FAKE_APK_PACKAGE_NAME"; private static final String APK2_PACKAGE_NAME = "com.android.FAKE_APK2_PACKAGE_NAME"; private static final String PERSISTENT_APK_PACKAGE_NAME = "com.android.PERSISTENT_PACKAGE_NAME"; @@ -78,6 +84,7 @@ public class InstallApexModuleTargetPreparerTest { private static final String APEX_PACKAGE_KEYWORD = "FAKE_APEX_PACKAGE_NAME"; private static final long APEX_VERSION = 1; private static final String APEX_NAME = "fakeApex.apex"; + private static final String APEX2_NAME = "fakeApex_2.apex"; private static final String APK_NAME = "fakeApk.apk"; private static final String APK2_NAME = "fakeSecondApk.apk"; private static final String PERSISTENT_APK_NAME = "fakePersistentApk.apk"; @@ -92,6 +99,8 @@ public class InstallApexModuleTargetPreparerTest { @Before public void setUp() throws Exception { mFakeApex = FileUtil.createTempFile("fakeApex", ".apex"); + mFakeApex2 = FileUtil.createTempFile("fakeApex_2", ".apex"); + mFakeApex3 = FileUtil.createTempFile("fakeApex_3", ".apex"); mFakeApk = FileUtil.createTempFile("fakeApk", ".apk"); mFakeApk2 = FileUtil.createTempFile("fakeSecondApk", ".apk"); mFakePersistentApk = FileUtil.createTempFile("fakePersistentApk", ".apk"); @@ -126,7 +135,13 @@ public class InstallApexModuleTargetPreparerTest { @Override protected File getLocalPathForFilename( TestInformation testInfo, String appFileName) throws TargetSetupError { - if (APEX_NAME.equals(appFileName)) { + if (appFileName.endsWith(".apex")) { + if (appFileName.contains("fakeApex_2")) { + return mFakeApex2; + } + else if (appFileName.contains("fakeApex_3")) { + return mFakeApex3; + } return mFakeApex; } if (appFileName.endsWith(".apk")) { @@ -138,13 +153,11 @@ public class InstallApexModuleTargetPreparerTest { return mFakeApk; } } - if (appFileName.endsWith(".apks")) { - if (appFileName.contains("Apex")) { - return mFakeApexApks; - } - if (appFileName.contains("Apk")) { - return mFakeApkApks; - } + if (SPLIT_APEX_APKS_NAME.equals(appFileName)) { + return mFakeApexApks; + } + if (SPLIT_APK__APKS_NAME.equals(appFileName)) { + return mFakeApkApks; } if (appFileName.endsWith(".jar")) { return mBundletoolJar; @@ -156,6 +169,12 @@ public class InstallApexModuleTargetPreparerTest { protected String parsePackageName( File testAppFile, DeviceDescriptor deviceDescriptor) { if (testAppFile.getName().endsWith(".apex")) { + if (testAppFile.getName().contains("fakeApex_2")) { + return APEX2_PACKAGE_NAME; + } + else if (testAppFile.getName().contains("fakeApex_3")) { + return APEX3_PACKAGE_NAME; + } return APEX_PACKAGE_NAME; } if (testAppFile.getName().endsWith(".apk") @@ -185,6 +204,8 @@ public class InstallApexModuleTargetPreparerTest { ApexInfo apexInfo; if (apex.getName().contains("Split")) { apexInfo = new ApexInfo(SPLIT_APEX_PACKAGE_NAME, APEX_VERSION); + } else if (apex.getName().contains("fakeApex_2")) { + apexInfo = new ApexInfo(APEX2_PACKAGE_NAME, APEX_VERSION); } else { apexInfo = new ApexInfo(APEX_PACKAGE_NAME, APEX_VERSION); } @@ -209,11 +230,343 @@ public class InstallApexModuleTargetPreparerTest { @After public void tearDown() throws Exception { FileUtil.deleteFile(mFakeApex); + FileUtil.deleteFile(mFakeApex2); + FileUtil.deleteFile(mFakeApex3); FileUtil.deleteFile(mFakeApk); FileUtil.deleteFile(mFakeApk2); FileUtil.deleteFile(mFakePersistentApk); } + /** + * Test that it gets the correct apex files that are already installed on the /data directory. + */ + @Test + public void testGetApexInData() throws Exception { + Set<ApexInfo> activatedApex = new HashSet<ApexInfo>(); + Set<ApexInfo> expectedApex = new HashSet<ApexInfo>(); + + ApexInfo fakeApexData = + new ApexInfo( + APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"); + + ApexInfo fakeApexData2 = + new ApexInfo( + APEX2_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex"); + + ApexInfo fakeApexSystem = + new ApexInfo( + "com.android.FAKE_APEX3_PACKAGE_NAME", + 1, + "/system/apex/com.android.FAKE_APEX3_PACKAGE_NAME@1.apex"); + + activatedApex = new HashSet<>(Arrays.asList(fakeApexData, fakeApexData2, fakeApexSystem)); + expectedApex = new HashSet<>(Arrays.asList(fakeApexData, fakeApexData2)); + assertEquals(2, mInstallApexModuleTargetPreparer.getApexInData(activatedApex).size()); + assertEquals(expectedApex, mInstallApexModuleTargetPreparer.getApexInData(activatedApex)); + + activatedApex = new HashSet<>(Arrays.asList(fakeApexSystem)); + assertEquals(0, mInstallApexModuleTargetPreparer.getApexInData(activatedApex).size()); + } + + /** + * Test that it returns the correct files to be installed and uninstalled. + */ + @Test + public void testGetModulesToUninstall_NoneUninstallAndInstallFiles() throws Exception { + Set<ApexInfo> apexInData = new HashSet<>(); + List<File> testFiles = new ArrayList<>(); + testFiles.add(mFakeApex); + testFiles.add(mFakeApex2); + + ApexInfo fakeApexData = + new ApexInfo( + APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"); + + ApexInfo fakeApexData2 = + new ApexInfo( + APEX2_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex"); + + apexInData.add(fakeApexData); + apexInData.add(fakeApexData2); + + EasyMock.replay(mMockBuildInfo, mMockDevice); + Set<ApexInfo> results = mInstallApexModuleTargetPreparer.getModulesToUninstall( + apexInData, testFiles, mMockDevice); + + assertEquals(0, testFiles.size()); + assertEquals(0, results.size()); + EasyMock.verify(mMockBuildInfo, mMockDevice); + } + + /** + * Test that it returns the correct files to be installed and uninstalled. + */ + @Test + public void testGetModulesToUninstall_UninstallAndInstallFiles() throws Exception { + Set<ApexInfo> apexInData = new HashSet<>(); + List<File> testFiles = new ArrayList<>(); + testFiles.add(mFakeApex3); + + ApexInfo fakeApexData = + new ApexInfo( + APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"); + + ApexInfo fakeApexData2 = + new ApexInfo( + APEX2_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex"); + + apexInData.add(fakeApexData); + apexInData.add(fakeApexData2); + + EasyMock.replay(mMockBuildInfo, mMockDevice); + Set<ApexInfo> results = mInstallApexModuleTargetPreparer.getModulesToUninstall( + apexInData, testFiles, mMockDevice); + assertEquals(1, testFiles.size()); + assertEquals(mFakeApex3, testFiles.get(0)); + assertEquals(2, results.size()); + results.containsAll(apexInData); + EasyMock.verify(mMockBuildInfo, mMockDevice); + } + + /** + * Test that it returns the correct files to be installed and uninstalled. + */ + @Test + public void testGetModulesToUninstall_UninstallAndInstallFiles2() throws Exception { + Set<ApexInfo> apexInData = new HashSet<>(); + List<File> testFiles = new ArrayList<>(); + testFiles.add(mFakeApex2); + testFiles.add(mFakeApex3); + + ApexInfo fakeApexData = + new ApexInfo( + APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"); + + ApexInfo fakeApexData2 = + new ApexInfo( + APEX2_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex"); + + apexInData.add(fakeApexData); + apexInData.add(fakeApexData2); + + EasyMock.replay(mMockDevice); + Set<ApexInfo> results = + mInstallApexModuleTargetPreparer.getModulesToUninstall( + apexInData, testFiles, mMockDevice); + assertEquals(1, testFiles.size()); + assertEquals(mFakeApex3, testFiles.get(0)); + assertEquals(1, results.size()); + results.contains(fakeApexData); + EasyMock.verify(mMockDevice); + } + + /** + * Test the method behaves the same process when the files to be installed contain apk or apks. + */ + @Test + public void testSetupAndTearDown_Optimize_APEXANDAPK_Reboot() throws Exception { + mSetter.setOptionValue("skip-apex-teardown", "true"); + mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME); + mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME); + + mMockDevice.deleteFile(APEX_DATA_DIR + "*"); + EasyMock.expectLastCall().times(1); + mMockDevice.deleteFile(SESSION_DATA_DIR + "*"); + EasyMock.expectLastCall().times(1); + mMockDevice.deleteFile(STAGING_DATA_DIR + "*"); + EasyMock.expectLastCall().times(1); + CommandResult res = new CommandResult(); + res.setStdout("test.apex"); + EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res); + EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res); + EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res); + mMockDevice.reboot(); + EasyMock.expectLastCall(); + + ApexInfo fakeApexData = + new ApexInfo( + APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"); + EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(new HashSet<>()).times(2); + EasyMock.expect(mMockDevice.getActiveApexes()).andReturn( + new HashSet<>(Arrays.asList(fakeApexData))).atLeastOnce(); + mockSuccessfulInstallMultiPackageAndReboot(); + Set<String> installableModules = new HashSet<>(); + installableModules.add(APK_PACKAGE_NAME); + installableModules.add(APEX_PACKAGE_NAME); + EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules); + EasyMock.replay(mMockDevice); + mInstallApexModuleTargetPreparer.setUp(mTestInfo); + EasyMock.verify(mMockDevice); + } + + /** + * Test the method will optimize the process and it will not reboot because the files to be + * installed are already installed on the device. + */ + @Test + public void testSetupAndTearDown_Optimize_MultipleAPEX_NoReboot() throws Exception { + mSetter.setOptionValue("skip-apex-teardown", "true"); + mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME); + mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME); + + Set<ApexInfo> apexInData = new HashSet<>(); + ApexInfo fakeApexData = + new ApexInfo( + APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"); + + ApexInfo fakeApexData2 = + new ApexInfo( + APEX2_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex"); + + apexInData.add(fakeApexData); + apexInData.add(fakeApexData2); + EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(apexInData).times(2); + Set<String> installableModules = new HashSet<>(); + installableModules.add(APEX_PACKAGE_NAME); + installableModules.add(APEX2_PACKAGE_NAME); + EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules); + EasyMock.replay(mMockDevice); + mInstallApexModuleTargetPreparer.setUp(mTestInfo); + EasyMock.verify(mMockDevice); + } + + /** + * Test the method will uninstall the unused files and install the required files for the + * current test, and finally reboot the device. + */ + @Test + public void testSetupAndTearDown_Optimize_MultipleAPEX_UninstallThenInstallAndReboot() + throws Exception { + mSetter.setOptionValue("skip-apex-teardown", "true"); + mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME); + + ApexInfo fakeApexData = + new ApexInfo( + APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"); + + ApexInfo fakeApexData2 = + new ApexInfo( + APEX2_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex"); + + EasyMock.expect(mMockDevice.getActiveApexes()).andReturn( + new HashSet<>(Arrays.asList(fakeApexData))).times(2); + EasyMock.expect(mMockDevice.getActiveApexes()).andReturn( + new HashSet<>(Arrays.asList(fakeApexData2))).atLeastOnce(); + Set<String> installableModules = new HashSet<>(); + installableModules.add(APEX2_PACKAGE_NAME); + EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules); + EasyMock.expect(mMockDevice.uninstallPackage(EasyMock.anyObject())) + .andReturn(null) + .once(); + mockSuccessfulInstallPackageAndReboot(mFakeApex2); + EasyMock.replay(mMockDevice); + mInstallApexModuleTargetPreparer.setUp(mTestInfo); + mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null); + EasyMock.verify(mMockDevice); + } + + /** + * Test the method will uninstall the unused files for the current test, and finally reboot the + * device. + */ + @Test + public void testSetupAndTearDown_Optimize_MultipleAPEX_UninstallAndReboot() throws Exception { + mSetter.setOptionValue("skip-apex-teardown", "true"); + mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME); + + ApexInfo fakeApexData = + new ApexInfo( + APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"); + + ApexInfo fakeApexData2 = + new ApexInfo( + APEX2_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex"); + + EasyMock.expect(mMockDevice.getActiveApexes()).andReturn( + new HashSet<>(Arrays.asList(fakeApexData, fakeApexData2))).times(2); + Set<String> installableModules = new HashSet<>(); + installableModules.add(APEX2_PACKAGE_NAME); + EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules); + EasyMock.expect(mMockDevice.uninstallPackage(EasyMock.anyObject())) + .andReturn(null) + .once(); + mMockDevice.reboot(); + EasyMock.expectLastCall().once(); + EasyMock.replay(mMockDevice); + mInstallApexModuleTargetPreparer.setUp(mTestInfo); + mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null); + EasyMock.verify(mMockDevice); + } + + /** + * Test the method will install the required files for the current test, and finally reboot the + * device. + */ + @Test + public void testSetupAndTearDown_Optimize_MultipleAPEX_Reboot() throws Exception { + mSetter.setOptionValue("skip-apex-teardown", "true"); + mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME); + mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME); + + Set<ApexInfo> apexInData = new HashSet<>(); + ApexInfo fakeApexData = + new ApexInfo( + APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"); + + ApexInfo fakeApexData2 = + new ApexInfo( + APEX2_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex"); + + apexInData.add(fakeApexData); + apexInData.add(fakeApexData2); + EasyMock.expect(mMockDevice.getActiveApexes()).andReturn( + new HashSet<>(Arrays.asList(fakeApexData))).times(2); + EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(apexInData).atLeastOnce(); + Set<String> installableModules = new HashSet<>(); + installableModules.add(APEX_PACKAGE_NAME); + installableModules.add(APEX2_PACKAGE_NAME); + EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules); + mockSuccessfulInstallPackageAndReboot(mFakeApex2); + EasyMock.replay(mMockDevice); + mInstallApexModuleTargetPreparer.setUp(mTestInfo); + mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null); + EasyMock.verify(mMockDevice); + } + @Test public void testSetupSuccess_removeExistingStagedApexSuccess() throws Exception { mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME); @@ -781,19 +1134,7 @@ public class InstallApexModuleTargetPreparerTest { @Test public void testSetupAndTearDown() throws Exception { mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME); - mMockDevice.deleteFile(APEX_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - mMockDevice.deleteFile(SESSION_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - mMockDevice.deleteFile(STAGING_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - CommandResult res = new CommandResult(); - res.setStdout("test.apex"); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res); - mMockDevice.reboot(); - EasyMock.expectLastCall(); + mockCleanInstalledApexPackagesAndReboot(); mockSuccessfulInstallPackageAndReboot(mFakeApex); Set<ApexInfo> activatedApex = new HashSet<ApexInfo>(); activatedApex.add( @@ -834,19 +1175,7 @@ public class InstallApexModuleTargetPreparerTest { public void testSetupAndTearDown_MultiInstall() throws Exception { mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME); mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME); - mMockDevice.deleteFile(APEX_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - mMockDevice.deleteFile(SESSION_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - mMockDevice.deleteFile(STAGING_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - CommandResult res = new CommandResult(); - res.setStdout("test.apex"); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res); - mMockDevice.reboot(); - EasyMock.expectLastCall(); + mockCleanInstalledApexPackagesAndReboot(); mockSuccessfulInstallMultiPackageAndReboot(); Set<ApexInfo> activatedApex = new HashSet<ApexInfo>(); activatedApex.add( @@ -891,22 +1220,7 @@ public class InstallApexModuleTargetPreparerTest { mBundletoolJar = File.createTempFile("bundletool", ".jar"); File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks); try { - mMockDevice.deleteFile(APEX_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - mMockDevice.deleteFile(SESSION_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - mMockDevice.deleteFile(STAGING_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - CommandResult res = new CommandResult(); - res.setStdout("test.apex"); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)) - .andReturn(res); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)) - .andReturn(res); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)) - .andReturn(res); - mMockDevice.reboot(); - EasyMock.expectLastCall(); + mockCleanInstalledApexPackagesAndReboot(); when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class))) .thenReturn("serial.json"); @@ -998,6 +1312,340 @@ public class InstallApexModuleTargetPreparerTest { } @Test + public void testInstallUsingBundletool_AbsolutePath() throws Exception { + mInstallApexModuleTargetPreparer.addTestFileName(SPLIT_APEX_APKS_NAME); + mInstallApexModuleTargetPreparer.addTestFileName(SPLIT_APK__APKS_NAME); + mFakeApexApks = File.createTempFile("fakeApex", ".apks"); + mFakeApkApks = File.createTempFile("fakeApk", ".apks"); + + File fakeSplitApexApks = File.createTempFile("ApexSplits", ""); + fakeSplitApexApks.delete(); + fakeSplitApexApks.mkdir(); + File splitApex = File.createTempFile("fakeSplitApex", ".apex", fakeSplitApexApks); + + File fakeSplitApkApks = File.createTempFile("ApkSplits", ""); + fakeSplitApkApks.delete(); + fakeSplitApkApks.mkdir(); + File splitApk1 = File.createTempFile("fakeSplitApk1", ".apk", fakeSplitApkApks); + mBundletoolJar = File.createTempFile("/fake/absolute/path/bundletool", ".jar"); + File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks); + try { + mockCleanInstalledApexPackagesAndReboot(); + when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class))) + .thenReturn("serial.json"); + + assertTrue(fakeSplitApexApks != null); + assertTrue(fakeSplitApkApks != null); + assertTrue(mFakeApexApks != null); + assertTrue(mFakeApkApks != null); + assertEquals(1, fakeSplitApexApks.listFiles().length); + assertEquals(2, fakeSplitApkApks.listFiles().length); + + when(mMockBundletoolUtil.extractSplitsFromApks( + Mockito.eq(mFakeApexApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class))) + .thenReturn(fakeSplitApexApks); + + when(mMockBundletoolUtil.extractSplitsFromApks( + Mockito.eq(mFakeApkApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class))) + .thenReturn(fakeSplitApkApks); + + mMockDevice.waitForDeviceAvailable(); + + List<String> trainInstallCmd = new ArrayList<>(); + trainInstallCmd.add("install-multi-package"); + trainInstallCmd.add(splitApex.getAbsolutePath()); + String cmd = ""; + for (File f : fakeSplitApkApks.listFiles()) { + if (!cmd.isEmpty()) { + cmd += ":" + f.getParentFile().getAbsolutePath() + "/" + f.getName(); + } else { + cmd += f.getParentFile().getAbsolutePath() + "/" + f.getName(); + } + } + trainInstallCmd.add(cmd); + EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0]))) + .andReturn("Success") + .once(); + mMockDevice.reboot(); + Set<ApexInfo> activatedApex = new HashSet<ApexInfo>(); + activatedApex.add( + new ApexInfo( + SPLIT_APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex")); + EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(activatedApex).times(3); + EasyMock.expect(mMockDevice.uninstallPackage(SPLIT_APK_PACKAGE_NAME)) + .andReturn(null) + .once(); + mMockDevice.reboot(); + EasyMock.expectLastCall(); + Set<String> installableModules = new HashSet<>(); + installableModules.add(APEX_PACKAGE_NAME); + installableModules.add(SPLIT_APK_PACKAGE_NAME); + EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules); + + EasyMock.replay(mMockBuildInfo, mMockDevice); + mInstallApexModuleTargetPreparer.setUp(mTestInfo); + mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null); + Mockito.verify(mMockBundletoolUtil, times(1)) + .generateDeviceSpecFile(Mockito.any(ITestDevice.class)); + // Extract splits 1 time to get the package name for the module, and again during + // installation. + Mockito.verify(mMockBundletoolUtil, times(2)) + .extractSplitsFromApks( + Mockito.eq(mFakeApexApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class)); + Mockito.verify(mMockBundletoolUtil, times(2)) + .extractSplitsFromApks( + Mockito.eq(mFakeApkApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class)); + EasyMock.verify(mMockBuildInfo, mMockDevice); + } finally { + FileUtil.deleteFile(mFakeApexApks); + FileUtil.deleteFile(mFakeApkApks); + FileUtil.recursiveDelete(fakeSplitApexApks); + FileUtil.deleteFile(fakeSplitApexApks); + FileUtil.recursiveDelete(fakeSplitApkApks); + FileUtil.deleteFile(fakeSplitApkApks); + FileUtil.deleteFile(mBundletoolJar); + } + } + + @Test + public void testInstallUsingBundletool_TrainFolder() throws Exception { + File trainFolder = File.createTempFile("tmpTrain", ""); + trainFolder.delete(); + trainFolder.mkdir(); + mSetter.setOptionValue("train-path", trainFolder.getAbsolutePath()); + mFakeApexApks = File.createTempFile("fakeApex", ".apks", trainFolder); + mFakeApkApks = File.createTempFile("fakeApk", ".apks", trainFolder); + + File fakeSplitApexApks = File.createTempFile("ApexSplits", ""); + fakeSplitApexApks.delete(); + fakeSplitApexApks.mkdir(); + File splitApex = File.createTempFile("fakeSplitApex", ".apex", fakeSplitApexApks); + + File fakeSplitApkApks = File.createTempFile("ApkSplits", ""); + fakeSplitApkApks.delete(); + fakeSplitApkApks.mkdir(); + File splitApk1 = File.createTempFile("fakeSplitApk1", ".apk", fakeSplitApkApks); + mBundletoolJar = File.createTempFile("bundletool", ".jar"); + File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks); + try { + mockCleanInstalledApexPackagesAndReboot(); + when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class))) + .thenReturn("serial.json"); + + assertTrue(fakeSplitApexApks != null); + assertTrue(fakeSplitApkApks != null); + assertTrue(mFakeApexApks != null); + assertTrue(mFakeApkApks != null); + assertEquals(1, fakeSplitApexApks.listFiles().length); + assertEquals(2, fakeSplitApkApks.listFiles().length); + + when(mMockBundletoolUtil.extractSplitsFromApks( + Mockito.eq(mFakeApexApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class))) + .thenReturn(fakeSplitApexApks); + + when(mMockBundletoolUtil.extractSplitsFromApks( + Mockito.eq(mFakeApkApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class))) + .thenReturn(fakeSplitApkApks); + + mMockDevice.waitForDeviceAvailable(); + + List<String> trainInstallCmd = new ArrayList<>(); + trainInstallCmd.add("install-multi-package"); + trainInstallCmd.add(splitApex.getAbsolutePath()); + String cmd = ""; + for (File f : fakeSplitApkApks.listFiles()) { + if (!cmd.isEmpty()) { + cmd += ":" + f.getParentFile().getAbsolutePath() + "/" + f.getName(); + } else { + cmd += f.getParentFile().getAbsolutePath() + "/" + f.getName(); + } + } + trainInstallCmd.add(cmd); + EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0]))) + .andReturn("Success") + .once(); + mMockDevice.reboot(); + Set<ApexInfo> activatedApex = new HashSet<ApexInfo>(); + activatedApex.add( + new ApexInfo( + SPLIT_APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex")); + EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(activatedApex).times(3); + EasyMock.expect(mMockDevice.uninstallPackage(SPLIT_APK_PACKAGE_NAME)) + .andReturn(null) + .once(); + mMockDevice.reboot(); + EasyMock.expectLastCall(); + Set<String> installableModules = new HashSet<>(); + installableModules.add(APEX_PACKAGE_NAME); + installableModules.add(SPLIT_APK_PACKAGE_NAME); + EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules); + + EasyMock.replay(mMockBuildInfo, mMockDevice); + mInstallApexModuleTargetPreparer.setUp(mTestInfo); + mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null); + Mockito.verify(mMockBundletoolUtil, times(1)) + .generateDeviceSpecFile(Mockito.any(ITestDevice.class)); + // Extract splits 1 time to get the package name for the module, and again during + // installation. + Mockito.verify(mMockBundletoolUtil, times(2)) + .extractSplitsFromApks( + Mockito.eq(mFakeApexApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class)); + Mockito.verify(mMockBundletoolUtil, times(2)) + .extractSplitsFromApks( + Mockito.eq(mFakeApkApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class)); + EasyMock.verify(mMockBuildInfo, mMockDevice); + } finally { + FileUtil.recursiveDelete(trainFolder); + FileUtil.deleteFile(trainFolder); + FileUtil.deleteFile(mFakeApexApks); + FileUtil.deleteFile(mFakeApkApks); + FileUtil.recursiveDelete(fakeSplitApexApks); + FileUtil.deleteFile(fakeSplitApexApks); + FileUtil.recursiveDelete(fakeSplitApkApks); + FileUtil.deleteFile(fakeSplitApkApks); + FileUtil.deleteFile(mBundletoolJar); + } + } + + @Test + public void testInstallUsingBundletool_AllFilesHaveAbsolutePath() throws Exception { + mFakeApexApks = File.createTempFile("fakeApex", ".apks"); + mFakeApkApks = File.createTempFile("fakeApk", ".apks"); + mInstallApexModuleTargetPreparer.addTestFile(mFakeApexApks); + mInstallApexModuleTargetPreparer.addTestFile(mFakeApkApks); + + File fakeSplitApexApks = File.createTempFile("ApexSplits", ""); + fakeSplitApexApks.delete(); + fakeSplitApexApks.mkdir(); + File splitApex = File.createTempFile("fakeSplitApex", ".apex", fakeSplitApexApks); + + File fakeSplitApkApks = File.createTempFile("ApkSplits", ""); + fakeSplitApkApks.delete(); + fakeSplitApkApks.mkdir(); + File splitApk1 = File.createTempFile("fakeSplitApk1", ".apk", fakeSplitApkApks); + mBundletoolJar = File.createTempFile("/fake/absolute/path/bundletool", ".jar"); + File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks); + try { + mockCleanInstalledApexPackagesAndReboot(); + when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class))) + .thenReturn("serial.json"); + + assertTrue(fakeSplitApexApks != null); + assertTrue(fakeSplitApkApks != null); + assertTrue(mFakeApexApks != null); + assertTrue(mFakeApkApks != null); + assertEquals(1, fakeSplitApexApks.listFiles().length); + assertEquals(2, fakeSplitApkApks.listFiles().length); + + when(mMockBundletoolUtil.extractSplitsFromApks( + Mockito.eq(mFakeApexApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class))) + .thenReturn(fakeSplitApexApks); + + when(mMockBundletoolUtil.extractSplitsFromApks( + Mockito.eq(mFakeApkApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class))) + .thenReturn(fakeSplitApkApks); + + mMockDevice.waitForDeviceAvailable(); + + List<String> trainInstallCmd = new ArrayList<>(); + trainInstallCmd.add("install-multi-package"); + trainInstallCmd.add(splitApex.getAbsolutePath()); + String cmd = ""; + for (File f : fakeSplitApkApks.listFiles()) { + if (!cmd.isEmpty()) { + cmd += ":" + f.getParentFile().getAbsolutePath() + "/" + f.getName(); + } else { + cmd += f.getParentFile().getAbsolutePath() + "/" + f.getName(); + } + } + trainInstallCmd.add(cmd); + EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0]))) + .andReturn("Success") + .once(); + mMockDevice.reboot(); + Set<ApexInfo> activatedApex = new HashSet<ApexInfo>(); + activatedApex.add( + new ApexInfo( + SPLIT_APEX_PACKAGE_NAME, + 1, + "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex")); + EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(activatedApex).times(3); + EasyMock.expect(mMockDevice.uninstallPackage(SPLIT_APK_PACKAGE_NAME)) + .andReturn(null) + .once(); + mMockDevice.reboot(); + EasyMock.expectLastCall(); + Set<String> installableModules = new HashSet<>(); + installableModules.add(APEX_PACKAGE_NAME); + installableModules.add(SPLIT_APK_PACKAGE_NAME); + EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules); + + EasyMock.replay(mMockBuildInfo, mMockDevice); + mInstallApexModuleTargetPreparer.setUp(mTestInfo); + mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null); + Mockito.verify(mMockBundletoolUtil, times(1)) + .generateDeviceSpecFile(Mockito.any(ITestDevice.class)); + // Extract splits 1 time to get the package name for the module, and again during + // installation. + Mockito.verify(mMockBundletoolUtil, times(2)) + .extractSplitsFromApks( + Mockito.eq(mFakeApexApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class)); + Mockito.verify(mMockBundletoolUtil, times(2)) + .extractSplitsFromApks( + Mockito.eq(mFakeApkApks), + Mockito.anyString(), + Mockito.any(ITestDevice.class), + Mockito.any(IBuildInfo.class)); + EasyMock.verify(mMockBuildInfo, mMockDevice); + } finally { + FileUtil.deleteFile(mFakeApexApks); + FileUtil.deleteFile(mFakeApkApks); + FileUtil.recursiveDelete(fakeSplitApexApks); + FileUtil.deleteFile(fakeSplitApexApks); + FileUtil.recursiveDelete(fakeSplitApkApks); + FileUtil.deleteFile(fakeSplitApkApks); + FileUtil.deleteFile(mBundletoolJar); + } + } + + @Test public void testInstallUsingBundletool_skipModuleNotPreloaded() throws Exception { mSetter.setOptionValue("ignore-if-module-not-preloaded", "true"); mInstallApexModuleTargetPreparer.addTestFileName(SPLIT_APEX_APKS_NAME); @@ -1069,6 +1717,7 @@ public class InstallApexModuleTargetPreparerTest { .once(); Set<String> installableModules = new HashSet<>(); installableModules.add(SPLIT_APK_PACKAGE_NAME); + EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules); EasyMock.replay(mMockBuildInfo, mMockDevice); @@ -1148,6 +1797,22 @@ public class InstallApexModuleTargetPreparerTest { EasyMock.expectLastCall().once(); } + private void mockCleanInstalledApexPackagesAndReboot() throws DeviceNotAvailableException { + mMockDevice.deleteFile(APEX_DATA_DIR + "*"); + EasyMock.expectLastCall().times(2); + mMockDevice.deleteFile(SESSION_DATA_DIR + "*"); + EasyMock.expectLastCall().times(2); + mMockDevice.deleteFile(STAGING_DATA_DIR + "*"); + EasyMock.expectLastCall().times(2); + CommandResult res = new CommandResult(); + res.setStdout("test.apex"); + EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res); + EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res); + EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res); + mMockDevice.reboot(); + EasyMock.expectLastCall(); + } + @Test public void testSetupAndTearDown_noModulesPreloaded() throws Exception { mSetter.setOptionValue("ignore-if-module-not-preloaded", "true"); @@ -1183,19 +1848,7 @@ public class InstallApexModuleTargetPreparerTest { mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME); // Module not preloaded. mInstallApexModuleTargetPreparer.addTestFileName(APK2_NAME); - mMockDevice.deleteFile(APEX_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - mMockDevice.deleteFile(SESSION_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - mMockDevice.deleteFile(STAGING_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - CommandResult res = new CommandResult(); - res.setStdout("test.apex"); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res); - mMockDevice.reboot(); - EasyMock.expectLastCall(); + mockCleanInstalledApexPackagesAndReboot(); mockSuccessfulInstallMultiPackageAndReboot(); Set<ApexInfo> activatedApex = new HashSet<ApexInfo>(); activatedApex.add( @@ -1225,19 +1878,7 @@ public class InstallApexModuleTargetPreparerTest { mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME); mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME); mInstallApexModuleTargetPreparer.addTestFileName(APK2_NAME); - mMockDevice.deleteFile(APEX_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - mMockDevice.deleteFile(SESSION_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - mMockDevice.deleteFile(STAGING_DATA_DIR + "*"); - EasyMock.expectLastCall().times(2); - CommandResult res = new CommandResult(); - res.setStdout("test.apex"); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res); - EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res); - mMockDevice.reboot(); - EasyMock.expectLastCall(); + mockCleanInstalledApexPackagesAndReboot(); Set<ApexInfo> activatedApex = new HashSet<ApexInfo>(); activatedApex.add( new ApexInfo( diff --git a/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java b/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java index 16ec66f9c..853803c3e 100644 --- a/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java +++ b/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java @@ -42,6 +42,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.IOException; import java.io.File; import java.util.Set; @@ -645,6 +646,53 @@ public class PushFilePreparerTest { } } + /** + * Test that if multiple files exists after delayed partial download, push the one with matching + * ABI. + */ + @Test + public void testPush_moduleName_files_abi_delayedDownload() throws Exception { + mOptionSetter.setOptionValue("push", "file->/data/local/tmp/file"); + mPreparer.setAbi(new Abi("x86", "32")); + + mPreparer.setInvocationContext(createModuleWithName("aaaaa")); + File tmpFolder = FileUtil.createTempDir("push-file-tests-dir"); + IDeviceBuildInfo info = + new DeviceBuildInfo() { + @Override + public File stageRemoteFile(String fileName, File workingDir) { + try { + File file_64 = + new File(tmpFolder, "target/testcases/aaaaa/x86_64/file"); + FileUtil.mkdirsRWX(file_64.getParentFile()); + file_64.createNewFile(); + File file_32 = new File(tmpFolder, "target/testcases/aaaaa/x86/file"); + FileUtil.mkdirsRWX(file_32.getParentFile()); + file_32.createNewFile(); + // Return the file with mismatched ABI. + return file_64; + } catch (IOException e) { + return null; + } + } + }; + try { + info.setFile(BuildInfoFileKey.TESTDIR_IMAGE, tmpFolder, "v1"); + EasyMock.expect( + mMockDevice.pushFile( + EasyMock.eq( + new File(tmpFolder, "target/testcases/aaaaa/x86/file")), + EasyMock.eq("/data/local/tmp/file"))) + .andReturn(true); + mTestInfo.getContext().addDeviceBuildInfo("device", info); + EasyMock.replay(mMockDevice); + mPreparer.setUp(mTestInfo); + EasyMock.verify(mMockDevice); + } finally { + FileUtil.recursiveDelete(tmpFolder); + } + } + @Test public void testPush_moduleName_ignored() throws Exception { mOptionSetter.setOptionValue("push", "lib64->/data/local/tmp/lib"); diff --git a/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java b/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java index 133bad598..20754fc89 100644 --- a/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java +++ b/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java @@ -21,7 +21,10 @@ import static org.easymock.EasyMock.anyObject; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.replay; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; import com.android.tradefed.build.BuildInfo; import com.android.tradefed.build.IBuildInfo; @@ -30,6 +33,8 @@ import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; import com.android.tradefed.util.IRunUtil; +import com.google.common.base.Throwables; + import junit.framework.TestCase; import java.io.File; @@ -105,4 +110,44 @@ public class PythonVirtualenvPreparerTest extends TestCase { mPreparer.installDeps(buildInfo, mMockDevice); assertTrue(buildInfo.getFile("PYTHONPATH") == null); } + + public void testStartVirtualenv_throwTSE_whenVirtualenvNotFound() throws Exception { + CommandResult result = new CommandResult(CommandStatus.SUCCESS); + result.setStdout("bash: virtualenv: command not found"); + expect(mMockRunUtil.runTimedCmd(anyLong(), eq("virtualenv"), eq("--version"))) + .andReturn(result); + replay(mMockRunUtil); + + try { + mPreparer.startVirtualenv(new BuildInfo(), mMockDevice); + fail("startVirtualenv succeeded despite a failed command"); + } catch (TargetSetupError e) { + assertThat( + String.format( + "An unexpected exception was thrown:\n%s", + Throwables.getStackTraceAsString(e)), + e.getMessage(), + containsString("virtualenv is not installed.")); + } + } + + public void testStartVirtualenv_throwTSE_whenVirtualenvIsTooOld() throws Exception { + CommandResult result = new CommandResult(CommandStatus.SUCCESS); + result.setStdout("virtualenv 16.7.10 from /path/to/site-packages/virtualenv/__init__.py"); + expect(mMockRunUtil.runTimedCmd(anyLong(), eq("virtualenv"), eq("--version"))) + .andReturn(result); + replay(mMockRunUtil); + + try { + mPreparer.startVirtualenv(new BuildInfo(), mMockDevice); + fail("startVirtualenv succeeded despite a failed command"); + } catch (TargetSetupError e) { + assertEquals( + String.format( + "An unexpected exception was thrown:\n%s", + Throwables.getStackTraceAsString(e)), + e.getMessage(), + "virtualenv is too old. Required: >=20.0.1, yours: 16.7.10"); + } + } }
\ No newline at end of file diff --git a/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java index 3f7ae6ddd..a8f95cf8d 100644 --- a/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java +++ b/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java @@ -20,24 +20,29 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.android.tradefed.config.OptionSetter; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.invoker.IInvocationContext; -import com.android.tradefed.invoker.InvocationContext; +import com.android.tradefed.device.IDeviceManager; import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; import com.android.tradefed.util.IRunUtil; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Answers; +import org.mockito.InOrder; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.io.OutputStream; import java.util.Arrays; @@ -45,21 +50,24 @@ import java.util.Collections; import java.util.List; /** Unit test for {@link RunHostCommandTargetPreparer}. */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(JUnit4.class) public final class RunHostCommandTargetPreparerTest { private static final String DEVICE_SERIAL = "123456"; private static final String FULL_COMMAND = "command \t\t\t \t argument $SERIAL"; - @Mock private ITestDevice mDevice; + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private TestInformation mTestInfo; + @Mock private RunHostCommandTargetPreparer.BgCommandLog mBgCommandLog; @Mock private IRunUtil mRunUtil; + @Mock private IDeviceManager mDeviceManager; private RunHostCommandTargetPreparer mPreparer; - private TestInformation mTestInfo; @Before public void setUp() { - when(mDevice.getSerialNumber()).thenReturn(DEVICE_SERIAL); mPreparer = new RunHostCommandTargetPreparer() { @Override @@ -68,13 +76,16 @@ public final class RunHostCommandTargetPreparerTest { } @Override + IDeviceManager getDeviceManager() { + return mDeviceManager; + } + + @Override protected List<BgCommandLog> createBgCommandLogs() { return Collections.singletonList(mBgCommandLog); } }; - IInvocationContext context = new InvocationContext(); - context.addAllocatedDevice("device", mDevice); - mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build(); + when(mTestInfo.getDevice().getSerialNumber()).thenReturn(DEVICE_SERIAL); } @Test @@ -89,6 +100,10 @@ public final class RunHostCommandTargetPreparerTest { // Verify timeout and command (split, removed whitespace, and device serial) mPreparer.setUp(mTestInfo); verify(mRunUtil).runTimedCmd(eq(10L), eq("command"), eq("argument"), eq(DEVICE_SERIAL)); + + // No flashing permit taken/returned by default + verify(mDeviceManager, never()).takeFlashingPermit(); + verify(mDeviceManager, never()).returnFlashingPermit(); } @Test @@ -120,6 +135,24 @@ public final class RunHostCommandTargetPreparerTest { } @Test + public void testSetUp_flashingPermit() throws Exception { + OptionSetter optionSetter = new OptionSetter(mPreparer); + optionSetter.setOptionValue("host-setup-command", FULL_COMMAND); + optionSetter.setOptionValue("use-flashing-permit", "true"); + + CommandResult result = new CommandResult(CommandStatus.SUCCESS); + when(mRunUtil.runTimedCmd(anyLong(), any())).thenReturn(result); + + // Verify command ran with flashing permit + mPreparer.setUp(mTestInfo); + InOrder inOrder = inOrder(mRunUtil, mDeviceManager); + inOrder.verify(mDeviceManager).takeFlashingPermit(); + inOrder.verify(mRunUtil) + .runTimedCmd(anyLong(), eq("command"), eq("argument"), eq(DEVICE_SERIAL)); + inOrder.verify(mDeviceManager).returnFlashingPermit(); + } + + @Test public void testTearDown() throws Exception { OptionSetter optionSetter = new OptionSetter(mPreparer); optionSetter.setOptionValue("host-teardown-command", FULL_COMMAND); @@ -131,6 +164,10 @@ public final class RunHostCommandTargetPreparerTest { // Verify timeout and command (split, removed whitespace, and device serial) mPreparer.tearDown(mTestInfo, null); verify(mRunUtil).runTimedCmd(eq(10L), eq("command"), eq("argument"), eq(DEVICE_SERIAL)); + + // No flashing permit taken/returned by default + verify(mDeviceManager, never()).takeFlashingPermit(); + verify(mDeviceManager, never()).returnFlashingPermit(); } @Test @@ -146,6 +183,24 @@ public final class RunHostCommandTargetPreparerTest { } @Test + public void testTearDown_flashingPermit() throws Exception { + OptionSetter optionSetter = new OptionSetter(mPreparer); + optionSetter.setOptionValue("host-teardown-command", FULL_COMMAND); + optionSetter.setOptionValue("use-flashing-permit", "true"); + + CommandResult result = new CommandResult(CommandStatus.SUCCESS); + when(mRunUtil.runTimedCmd(anyLong(), any())).thenReturn(result); + + // Verify command ran with flashing permit + mPreparer.tearDown(mTestInfo, null); + InOrder inOrder = inOrder(mRunUtil, mDeviceManager); + inOrder.verify(mDeviceManager).takeFlashingPermit(); + inOrder.verify(mRunUtil) + .runTimedCmd(anyLong(), eq("command"), eq("argument"), eq(DEVICE_SERIAL)); + inOrder.verify(mDeviceManager).returnFlashingPermit(); + } + + @Test public void testBgCommand() throws Exception { OptionSetter optionSetter = new OptionSetter(mPreparer); optionSetter.setOptionValue("host-background-command", FULL_COMMAND); diff --git a/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java index a623201f7..236498a11 100644 --- a/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java +++ b/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java @@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -42,6 +43,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Answers; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -117,6 +119,9 @@ public final class RunHostScriptTargetPreparerTest { verify(mRunUtil).runTimedCmd(10L, mScriptFile.getAbsolutePath()); // Verify that script is executable assertTrue(mScriptFile.canExecute()); + // No flashing permit taken/returned by default + verify(mDeviceManager, never()).takeFlashingPermit(); + verify(mDeviceManager, never()).returnFlashingPermit(); } @Test @@ -169,4 +174,16 @@ public final class RunHostScriptTargetPreparerTest { mPreparer.setUp(mTestInfo); verify(mRunUtil).setEnvVariable("PATH", expectedPath); } + + @Test + public void testSetUp_flashingPermit() throws Exception { + mOptionSetter.setOptionValue("script-file", mScriptFile.getAbsolutePath()); + mOptionSetter.setOptionValue("use-flashing-permit", "true"); + // Verify script executed with flashing permit + mPreparer.setUp(mTestInfo); + InOrder inOrder = inOrder(mRunUtil, mDeviceManager); + inOrder.verify(mDeviceManager).takeFlashingPermit(); + inOrder.verify(mRunUtil).runTimedCmd(anyLong(), eq(mScriptFile.getAbsolutePath())); + inOrder.verify(mDeviceManager).returnFlashingPermit(); + } } diff --git a/tests/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java new file mode 100644 index 000000000..14e324b94 --- /dev/null +++ b/tests/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.targetprep; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.android.tradefed.config.OptionSetter; +import com.android.tradefed.device.UserInfo; +import com.android.tradefed.invoker.TestInformation; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.HashMap; +import java.util.Map; + +@RunWith(JUnit4.class) +public class RunOnSecondaryUserTargetPreparerTest { + + private static final String CREATED_USER_2_MESSAGE = "Created user id 2"; + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private TestInformation mTestInfo; + + private RunOnSecondaryUserTargetPreparer mPreparer; + private OptionSetter mOptionSetter; + + @Before + public void setUp() throws Exception { + mPreparer = new RunOnSecondaryUserTargetPreparer(); + mOptionSetter = new OptionSetter(mPreparer); + } + + @Test + public void setUp_createsAndStartsSecondaryUser() throws Exception { + String expectedCreateUserCommand = "pm create-user secondary"; + String expectedStartUserCommand = "am start-user -w 2"; + when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand)) + .thenReturn(CREATED_USER_2_MESSAGE); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.getDevice()).executeShellCommand(expectedCreateUserCommand); + verify(mTestInfo.getDevice()).executeShellCommand(expectedStartUserCommand); + } + + @Test + public void setUp_secondaryUserAlreadyExists_doesNotCreateSecondaryUser() throws Exception { + Map<Integer, UserInfo> userInfos = new HashMap<>(); + userInfos.put(2, new UserInfo(2, "secondary", /* flag= */ 0, /* isRunning= */ true)); + when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.getDevice(), never()).executeShellCommand(any()); + } + + @Test + public void setUp_secondaryUserAlreadyExists_runsTestAsExistingUser() throws Exception { + Map<Integer, UserInfo> userInfos = new HashMap<>(); + userInfos.put(3, new UserInfo(3, "secondary", /* flag= */ 0, /* isRunning= */ true)); + when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.properties()) + .put(RunOnWorkProfileTargetPreparer.RUN_TESTS_AS_USER_KEY, "3"); + } + + @Test + public void setUp_setsRunTestsAsUser() throws Exception { + String expectedCreateUserCommand = "pm create-user secondary"; + when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand)) + .thenReturn(CREATED_USER_2_MESSAGE); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.properties()) + .put(RunOnSecondaryUserTargetPreparer.RUN_TESTS_AS_USER_KEY, "2"); + } + + @Test + public void setUp_secondaryUserAlreadyExists_installsPackagesInExistingUser() throws Exception { + Map<Integer, UserInfo> userInfos = new HashMap<>(); + userInfos.put(3, new UserInfo(3, "secondary", /* flag= */ 0, /* isRunning= */ true)); + when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos); + mOptionSetter.setOptionValue( + RunOnWorkProfileTargetPreparer.TEST_PACKAGE_NAME_OPTION, "com.android.testpackage"); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.getDevice()) + .executeShellCommand("pm install-existing --user 3 com.android.testpackage"); + } + + @Test + public void setUp_installsPackagesInSecondaryUser() throws Exception { + String expectedCreateUserCommand = "pm create-user secondary"; + when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand)) + .thenReturn(CREATED_USER_2_MESSAGE); + mOptionSetter.setOptionValue( + RunOnSecondaryUserTargetPreparer.TEST_PACKAGE_NAME_OPTION, + "com.android.testpackage"); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.getDevice()) + .executeShellCommand("pm install-existing --user 2 com.android.testpackage"); + } + + @Test + public void setUp_workProfileAlreadyExists_disablesTearDown() throws Exception { + Map<Integer, UserInfo> userInfos = new HashMap<>(); + userInfos.put(3, new UserInfo(3, "secondary", /* flag= */ 0, /* isRunning= */ true)); + when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos); + mOptionSetter.setOptionValue("disable-tear-down", "false"); + + mPreparer.setUp(mTestInfo); + + assertThat(mPreparer.isTearDownDisabled()).isTrue(); + } + + @Test + public void setUp_doesNotDisableTearDown() throws Exception { + String expectedCreateUserCommand = "pm create-user secondary"; + when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand)) + .thenReturn(CREATED_USER_2_MESSAGE); + mOptionSetter.setOptionValue("disable-tear-down", "false"); + + mPreparer.setUp(mTestInfo); + + assertThat(mPreparer.isTearDownDisabled()).isFalse(); + } + + @Test + public void tearDown_removesSecondaryUser() throws Exception { + when(mTestInfo.properties().get(RunOnSecondaryUserTargetPreparer.RUN_TESTS_AS_USER_KEY)) + .thenReturn("2"); + + mPreparer.tearDown(mTestInfo, /* throwable= */ null); + + verify(mTestInfo.getDevice()).removeUser(2); + } +} diff --git a/tests/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java new file mode 100644 index 000000000..5845cd275 --- /dev/null +++ b/tests/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.targetprep; + +import static com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer.RUN_TESTS_AS_USER_KEY; +import static com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer.TEST_PACKAGE_NAME_OPTION; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.android.tradefed.config.OptionSetter; +import com.android.tradefed.device.UserInfo; +import com.android.tradefed.invoker.TestInformation; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.HashMap; +import java.util.Map; + +@RunWith(JUnit4.class) +public class RunOnWorkProfileTargetPreparerTest { + private static final String CREATED_USER_10_MESSAGE = "Created user id 10"; + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private TestInformation mTestInfo; + + private RunOnWorkProfileTargetPreparer mPreparer; + private OptionSetter mOptionSetter; + + @Before + public void setUp() throws Exception { + mPreparer = new RunOnWorkProfileTargetPreparer(); + mOptionSetter = new OptionSetter(mPreparer); + } + + @Test + public void setUp_createsAndStartsWorkProfile() throws Exception { + String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work"; + String expectedStartUserCommand = "am start-user -w 10"; + when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand)) + .thenReturn(CREATED_USER_10_MESSAGE); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.getDevice()).executeShellCommand(expectedCreateUserCommand); + verify(mTestInfo.getDevice()).executeShellCommand(expectedStartUserCommand); + } + + @Test + public void setUp_workProfileAlreadyExists_doesNotCreateWorkProfile() throws Exception { + Map<Integer, UserInfo> userInfos = new HashMap<>(); + userInfos.put( + 10, + new UserInfo( + 10, + "work", + /* flag= */ UserInfo.FLAG_MANAGED_PROFILE, + /* isRunning= */ true)); + when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.getDevice(), never()).executeShellCommand(any()); + } + + @Test + public void setUp_nonZeroCurrentUser_createsWorkProfileForCorrectUser() throws Exception { + when(mTestInfo.getDevice().getCurrentUser()).thenReturn(1); + String expectedCreateUserCommand = "pm create-user --profileOf 1 --managed work"; + when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand)) + .thenReturn(CREATED_USER_10_MESSAGE); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.getDevice()).executeShellCommand(expectedCreateUserCommand); + } + + @Test + public void setUp_workProfileAlreadyExists_runsTestAsExistingUser() throws Exception { + Map<Integer, UserInfo> userInfos = new HashMap<>(); + userInfos.put( + 11, + new UserInfo( + 11, + "work", + /* flag= */ UserInfo.FLAG_MANAGED_PROFILE, + /* isRunning= */ true)); + when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.properties()).put(RUN_TESTS_AS_USER_KEY, "11"); + } + + @Test + public void setUp_setsRunTestsAsUser() throws Exception { + String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work"; + when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand)) + .thenReturn(CREATED_USER_10_MESSAGE); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.properties()).put(RUN_TESTS_AS_USER_KEY, "10"); + } + + @Test + public void setUp_workProfileAlreadyExists_installsPackagesInExistingUser() throws Exception { + Map<Integer, UserInfo> userInfos = new HashMap<>(); + userInfos.put( + 11, + new UserInfo( + 11, + "work", + /* flag= */ UserInfo.FLAG_MANAGED_PROFILE, + /* isRunning= */ true)); + when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos); + mOptionSetter.setOptionValue(TEST_PACKAGE_NAME_OPTION, "com.android.testpackage"); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.getDevice()) + .executeShellCommand("pm install-existing --user 11 com.android.testpackage"); + } + + @Test + public void setUp_installsPackagesInWorkUser() throws Exception { + String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work"; + when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand)) + .thenReturn(CREATED_USER_10_MESSAGE); + mOptionSetter.setOptionValue(TEST_PACKAGE_NAME_OPTION, "com.android.testpackage"); + + mPreparer.setUp(mTestInfo); + + verify(mTestInfo.getDevice()) + .executeShellCommand("pm install-existing --user 10 com.android.testpackage"); + } + + @Test + public void setUp_workProfileAlreadyExists_disablesTearDown() throws Exception { + Map<Integer, UserInfo> userInfos = new HashMap<>(); + userInfos.put( + 11, + new UserInfo( + 11, + "work", + /* flag= */ UserInfo.FLAG_MANAGED_PROFILE, + /* isRunning= */ true)); + when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos); + mOptionSetter.setOptionValue("disable-tear-down", "false"); + + mPreparer.setUp(mTestInfo); + + assertThat(mPreparer.isTearDownDisabled()).isTrue(); + } + + @Test + public void setUp_doesNotDisableTearDown() throws Exception { + String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work"; + when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand)) + .thenReturn(CREATED_USER_10_MESSAGE); + mOptionSetter.setOptionValue("disable-tear-down", "false"); + + mPreparer.setUp(mTestInfo); + + assertThat(mPreparer.isTearDownDisabled()).isFalse(); + } + + @Test + public void tearDown_removesWorkUser() throws Exception { + when(mTestInfo.properties().get(RUN_TESTS_AS_USER_KEY)).thenReturn("10"); + + mPreparer.tearDown(mTestInfo, /* throwable= */ null); + + verify(mTestInfo.getDevice()).removeUser(10); + } +} diff --git a/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java b/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java index 97dbc2bbe..7fa4796df 100644 --- a/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java +++ b/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java @@ -16,6 +16,7 @@ package com.android.tradefed.targetprep; +import static com.android.tradefed.targetprep.TestAppInstallSetup.CHECK_MIN_SDK_OPTION; import static com.android.tradefed.targetprep.TestAppInstallSetup.TEST_FILE_NAME_OPTION; import static com.android.tradefed.targetprep.TestAppInstallSetup.THROW_IF_NOT_FOUND_OPTION; @@ -23,8 +24,13 @@ import static com.google.common.truth.Truth.assertThat; import static org.easymock.EasyMock.anyBoolean; import static org.easymock.EasyMock.anyObject; + +import static org.mockito.Mockito.doReturn; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.times; import com.android.tradefed.build.IDeviceBuildInfo; import com.android.tradefed.command.remote.DeviceDescriptor; @@ -36,6 +42,7 @@ import com.android.tradefed.invoker.IInvocationContext; import com.android.tradefed.invoker.InvocationContext; import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.testtype.Abi; +import com.android.tradefed.util.AaptParser; import com.android.tradefed.util.FileUtil; import com.google.common.collect.ImmutableMap; @@ -47,12 +54,14 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.Mockito; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -68,6 +77,7 @@ public class TestAppInstallSetupTest { private File fakeApk; private File fakeApk2; private File mFakeBuildApk; + private AaptParser mMockAaptParser; private TestAppInstallSetup mPrep; private TestInformation mTestInfo; private IDeviceBuildInfo mMockBuildInfo; @@ -125,6 +135,7 @@ public class TestAppInstallSetupTest { mTestSplitApkFiles.add(fakeApk2); mMockBuildInfo = EasyMock.createMock(IDeviceBuildInfo.class); + mMockAaptParser = Mockito.mock(AaptParser.class); mMockTestDevice = EasyMock.createMock(ITestDevice.class); EasyMock.expect(mMockTestDevice.getSerialNumber()).andStubReturn(SERIAL); EasyMock.expect(mMockTestDevice.getDeviceDescriptor()).andStubReturn(null); @@ -572,6 +583,108 @@ public class TestAppInstallSetupTest { assertThat(installs).containsExactly(ImmutableSet.of(apkFile1, apkFile2)); } + /** + * Tests that we throw exception with check-min-sdk when file fails to parse + * + * @throws Exception the expected exception + */ + @Test + public void testResolveApkFiles_checkMinSdk_failParsing() throws Exception { + mPrep = + new TestAppInstallSetup() { + @Override + AaptParser doAaptParse(File apkFile) { + return null; + } + }; + List<File> files = new ArrayList<>(); + files.add(fakeApk); + OptionSetter setter = new OptionSetter(mPrep); + setter.setOptionValue(CHECK_MIN_SDK_OPTION, "true"); + EasyMock.replay(mMockBuildInfo, mMockTestDevice); + try { + mPrep.resolveApkFiles(mTestInfo, files); + fail("Should have thrown an exception"); + } catch (TargetSetupError expected) { + assertEquals( + String.format("Failed to extract info from `%s` using aapt", fakeApk.getName()), + expected.getMessage()); + } finally { + EasyMock.verify(mMockBuildInfo, mMockTestDevice); + } + } + + /** Tests that we don't include the file if api level too low */ + @Test + public void testResolveApkFiles_checkMinSdk_apiLow() throws Exception { + mPrep = + new TestAppInstallSetup() { + @Override + AaptParser doAaptParse(File apkFile) { + return mMockAaptParser; + } + + @Override + protected String parsePackageName( + File testAppFile, DeviceDescriptor deviceDescriptor) { + return "fakePackageName"; + } + }; + List<File> files = new ArrayList<>(); + files.add(fakeApk); + OptionSetter setter = new OptionSetter(mPrep); + setter.setOptionValue(CHECK_MIN_SDK_OPTION, "true"); + EasyMock.expect(mMockTestDevice.getSerialNumber()).andStubReturn(SERIAL); + EasyMock.expect(mMockTestDevice.getApiLevel()).andReturn(21).times(2); + doReturn(22).when(mMockAaptParser).getSdkVersion(); + + Map<File, String> expected = new LinkedHashMap<>(); + + EasyMock.replay(mMockBuildInfo, mMockTestDevice); + + Map<File, String> result = mPrep.resolveApkFiles(mTestInfo, files); + + EasyMock.verify(mMockBuildInfo, mMockTestDevice); + Mockito.verify(mMockAaptParser, times(2)).getSdkVersion(); + assertEquals(expected, result); + } + + /** Tests that we include the file if level is appropriate */ + @Test + public void testResolveApkFiles_checkMinSdk_apiOk() throws Exception { + mPrep = + new TestAppInstallSetup() { + @Override + AaptParser doAaptParse(File apkFile) { + return mMockAaptParser; + } + + @Override + protected String parsePackageName( + File testAppFile, DeviceDescriptor deviceDescriptor) { + return "fakePackageName"; + } + }; + List<File> files = new ArrayList<>(); + files.add(fakeApk); + OptionSetter setter = new OptionSetter(mPrep); + setter.setOptionValue(CHECK_MIN_SDK_OPTION, "true"); + EasyMock.expect(mMockTestDevice.getSerialNumber()).andStubReturn(SERIAL); + EasyMock.expect(mMockTestDevice.getApiLevel()).andReturn(23); + doReturn(22).when(mMockAaptParser).getSdkVersion(); + + Map<File, String> expected = new LinkedHashMap<>(); + expected.put(fakeApk, "fakePackageName"); + + EasyMock.replay(mMockBuildInfo, mMockTestDevice); + + Map<File, String> result = mPrep.resolveApkFiles(mTestInfo, files); + + Mockito.verify(mMockAaptParser).getSdkVersion(); + EasyMock.verify(mMockBuildInfo, mMockTestDevice); + assertEquals(result, expected); + } + private static Path createSubDirectory(Path parent, String name) throws IOException { return Files.createDirectory(parent.resolve(name)).toAbsolutePath(); } diff --git a/tests/src/com/android/tradefed/testtype/ArtGTestTest.java b/tests/src/com/android/tradefed/testtype/ArtGTestTest.java new file mode 100644 index 000000000..a30343ef7 --- /dev/null +++ b/tests/src/com/android/tradefed/testtype/ArtGTestTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.testtype; + +import com.android.ddmlib.IShellOutputReceiver; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.MockFileUtil; +import com.android.tradefed.targetprep.ArtChrootPreparer; +import com.android.tradefed.invoker.TestInformation; +import com.android.tradefed.result.ITestInvocationListener; + +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ArtGTest}. */ +@RunWith(JUnit4.class) +public class ArtGTestTest { + private ITestInvocationListener mMockInvocationListener = null; + private IShellOutputReceiver mMockReceiver = null; + private ITestDevice mMockITestDevice = null; + private GTest mGTest; + private TestInformation mTestInfo; + + @Before + public void setUp() throws Exception { + mMockInvocationListener = EasyMock.createMock(ITestInvocationListener.class); + mMockReceiver = EasyMock.createMock(IShellOutputReceiver.class); + mMockITestDevice = EasyMock.createMock(ITestDevice.class); + mGTest = + new ArtGTest() { + @Override + IShellOutputReceiver createResultParser( + String runName, ITestInvocationListener listener) { + return mMockReceiver; + } + }; + mGTest.setDevice(mMockITestDevice); + mGTest.setNativeTestDevicePath(ArtChrootPreparer.CHROOT_PATH); + mTestInfo = TestInformation.newBuilder().build(); + + EasyMock.expect(mMockITestDevice.getSerialNumber()).andStubReturn("serial"); + } + + private void replayMocks() { + EasyMock.replay(mMockInvocationListener, mMockITestDevice); + } + + private void verifyMocks() { + EasyMock.verify(mMockInvocationListener, mMockITestDevice); + } + + @Test + public void testChroot_testRun() throws DeviceNotAvailableException { + final String nativeTestPath = ArtChrootPreparer.CHROOT_PATH; + final String test1 = "test1"; + final String testPath1 = String.format("%s/%s", nativeTestPath, test1); + + MockFileUtil.setMockDirContents(mMockITestDevice, nativeTestPath, test1); + EasyMock.expect(mMockITestDevice.doesFileExist(nativeTestPath)).andReturn(true); + EasyMock.expect(mMockITestDevice.isDirectory(nativeTestPath)).andReturn(true); + EasyMock.expect(mMockITestDevice.isDirectory(testPath1)).andReturn(false); + EasyMock.expect(mMockITestDevice.isExecutable(testPath1)).andReturn(true); + + String[] files = new String[] {"test1"}; + EasyMock.expect(mMockITestDevice.getChildren(nativeTestPath)).andReturn(files); + mMockITestDevice.executeShellCommand( + EasyMock.startsWith("chroot /data/local/tmp/art-test-chroot /test1"), + EasyMock.anyObject(), + EasyMock.anyLong(), + EasyMock.anyObject(), + EasyMock.anyInt()); + + replayMocks(); + mGTest.run(mTestInfo, mMockInvocationListener); + verifyMocks(); + } +} diff --git a/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java b/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java index 23d8b7590..e7c435442 100644 --- a/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java +++ b/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java @@ -25,7 +25,6 @@ import com.android.tradefed.config.OptionSetter; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.invoker.TestInformation; -import com.android.tradefed.invoker.ExecutionFiles.FilesKey; import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.TestDescription; @@ -49,9 +48,6 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ArtRunTestTest { - // Default run-test name. - private static final String RUN_TEST_NAME = "run-test"; - private ITestInvocationListener mMockInvocationListener; private IAbi mMockAbi; private ITestDevice mMockITestDevice; @@ -60,9 +56,9 @@ public class ArtRunTestTest { private ArtRunTest mArtRunTest; private OptionSetter mSetter; private TestInformation mTestInfo; - // Target tests directory. - private File mTmpTargetTestsDir; - // Expected output file (under the target tests directory). + // Test dependencies directory on host. + private File mTmpDepsDir; + // Expected output file (within the dependencies directory). private File mTmpExpectedFile; @Before @@ -82,23 +78,22 @@ public class ArtRunTestTest { mArtRunTest.setDevice(mMockITestDevice); mSetter = new OptionSetter(mArtRunTest); - // Set up target tests directory and expected output file. - mTmpTargetTestsDir = FileUtil.createTempDir("target_testcases"); - File runTestDir = new File(mTmpTargetTestsDir, RUN_TEST_NAME); - runTestDir.mkdir(); - mTmpExpectedFile = new File(runTestDir, "expected.txt"); - FileWriter fw = new FileWriter(mTmpExpectedFile); - fw.write("output\n"); - fw.close(); - - // Set the target tests directory in test information object. - mTestInfo = TestInformation.newBuilder().build(); - mTestInfo.executionFiles().put(FilesKey.TARGET_TESTS_DIRECTORY, mTmpTargetTestsDir); + // Temporary test directory (e.g. for the expected-output file). + mTmpDepsDir = FileUtil.createTempDir("art-run-test-deps"); + mTestInfo = TestInformation.newBuilder().setDependenciesFolder(mTmpDepsDir).build(); } @After public void tearDown() { - FileUtil.recursiveDelete(mTmpTargetTestsDir); + FileUtil.recursiveDelete(mTmpDepsDir); + } + + /** Helper creating an expected-output file within the (temporary) test directory. */ + private void createExpectedOutputFile(String runTestName) throws IOException { + mTmpExpectedFile = new File(mTmpDepsDir, runTestName + "-expected.txt"); + FileWriter fw = new FileWriter(mTmpExpectedFile); + fw.write("output\n"); + fw.close(); } /** Helper mocking writing the output of a test command. */ @@ -151,7 +146,9 @@ public class ArtRunTestTest { @Test public void testRunSingleTest_unsetClasspathOption() throws ConfigurationException, DeviceNotAvailableException, IOException { - mSetter.setOptionValue("run-test-name", RUN_TEST_NAME); + final String runTestName = "test"; + mSetter.setOptionValue("run-test-name", runTestName); + createExpectedOutputFile(runTestName); replayMocks(); try { @@ -163,21 +160,20 @@ public class ArtRunTestTest { verifyMocks(); } - /** Test the run method for a (single) test. */ - @Test - public void testRunSingleTest() + /** Helper containing testing logic for a (single) test expected to run (and succeed). */ + private void doTestRunSingleTest(final String runTestName, final String classpath) throws ConfigurationException, DeviceNotAvailableException, IOException { - mSetter.setOptionValue("run-test-name", RUN_TEST_NAME); - final String classpath = "/data/local/tmp/test/test.jar"; + mSetter.setOptionValue("run-test-name", runTestName); + createExpectedOutputFile(runTestName); mSetter.setOptionValue("classpath", classpath); // Pre-test checks. - EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn(""); EasyMock.expect(mMockAbi.getName()).andReturn("abi"); + EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn(""); String runName = "ArtRunTest_abi"; // Beginning of test. mMockInvocationListener.testRunStarted(runName, 1); - TestDescription testId = new TestDescription(runName, RUN_TEST_NAME); + TestDescription testId = new TestDescription(runName, runTestName); mMockInvocationListener.testStarted(testId); String cmd = String.format("dalvikvm64 -classpath %s Main", classpath); // Test execution. @@ -198,6 +194,31 @@ public class ArtRunTestTest { verifyMocks(); } + /** Helper containing testing logic for a (single) test expected not to run. */ + private void doTestDoNotRunSingleTest(final String runTestName, final String classpath) + throws ConfigurationException, DeviceNotAvailableException, IOException { + mSetter.setOptionValue("run-test-name", runTestName); + createExpectedOutputFile(runTestName); + mSetter.setOptionValue("classpath", classpath); + + EasyMock.expect(mMockAbi.getName()).andReturn("abi"); + replayMocks(); + + mArtRunTest.run(mTestInfo, mMockInvocationListener); + + verifyMocks(); + } + + /** Test the run method for a (single) test. */ + @Test + public void testRunSingleTest() + throws ConfigurationException, DeviceNotAvailableException, IOException { + final String runTestName = "test"; + final String classpath = "/data/local/tmp/test/test.jar"; + + doTestRunSingleTest(runTestName, classpath); + } + /** * Test the behavior of the run method when the output produced by the shell command on device * differs from the expected output. @@ -205,17 +226,19 @@ public class ArtRunTestTest { @Test public void testRunSingleTest_unexpectedOutput() throws ConfigurationException, DeviceNotAvailableException, IOException { - mSetter.setOptionValue("run-test-name", RUN_TEST_NAME); + final String runTestName = "test"; + mSetter.setOptionValue("run-test-name", runTestName); + createExpectedOutputFile(runTestName); final String classpath = "/data/local/tmp/test/test.jar"; mSetter.setOptionValue("classpath", classpath); // Pre-test checks. - EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn(""); EasyMock.expect(mMockAbi.getName()).andReturn("abi"); + EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn(""); String runName = "ArtRunTest_abi"; // Beginning of test. mMockInvocationListener.testRunStarted(runName, 1); - TestDescription testId = new TestDescription(runName, RUN_TEST_NAME); + TestDescription testId = new TestDescription(runName, runTestName); mMockInvocationListener.testStarted(testId); String cmd = String.format("dalvikvm64 -classpath %s Main", classpath); // Test execution. @@ -224,7 +247,14 @@ public class ArtRunTestTest { mockTestOutputWrite("unexpected\n"); EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn(""); // End of test. - mMockInvocationListener.testFailed(testId, "'unexpected\n' instead of 'output\n'"); + String errorMessage = + "The test's standard output does not match the expected output:\n" + + "--- expected.txt\n" + + "+++ stdout\n" + + "@@ -1,1 +1,1 @@\n" + + "-output\n" + + "+unexpected\n"; + mMockInvocationListener.testFailed(testId, errorMessage); mMockInvocationListener.testEnded( EasyMock.eq(testId), (HashMap<String, Metric>) EasyMock.anyObject()); mMockInvocationListener.testRunEnded( @@ -236,4 +266,44 @@ public class ArtRunTestTest { verifyMocks(); } + + /** Test the run method for a (single) test contained in an include filter. */ + @Test + public void testIncludeFilter() + throws ConfigurationException, DeviceNotAvailableException, IOException { + final String runTestName = "test"; + final String classpath = "/data/local/tmp/test/test.jar"; + // Add an include filter containing the test's name. + mArtRunTest.addIncludeFilter(runTestName); + + doTestRunSingleTest(runTestName, classpath); + } + + /** Test the run method for a (single) test contained in an exclude filter. */ + @Test + public void testExcludeFilter() + throws ConfigurationException, DeviceNotAvailableException, IOException { + final String runTestName = "test"; + final String classpath = "/data/local/tmp/test/test.jar"; + // Add an exclude filter containing the test's name. + mArtRunTest.addExcludeFilter(runTestName); + + doTestDoNotRunSingleTest(runTestName, classpath); + } + + /** + * Test the run method for a (single) test contained both in an include and an exclude filter. + */ + @Test + public void testIncludeAndExcludeFilter() + throws ConfigurationException, DeviceNotAvailableException, IOException { + final String runTestName = "test"; + final String classpath = "/data/local/tmp/test/test.jar"; + // Add an include filter containing the test's name. + mArtRunTest.addIncludeFilter(runTestName); + // Add an exclude filter containing the test's name. + mArtRunTest.addExcludeFilter(runTestName); + + doTestDoNotRunSingleTest(runTestName, classpath); + } } diff --git a/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java b/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java index 51877ced1..8ce4d0a62 100644 --- a/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java +++ b/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java @@ -23,6 +23,7 @@ import com.android.tradefed.invoker.TestInformation; import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.TestDescription; +import com.android.tradefed.util.StringEscapeUtils; import junit.framework.TestCase; @@ -348,8 +349,7 @@ public class GoogleBenchmarkTestTest extends TestCase { String filterFlag = mGoogleBenchmarkTest.getFilterFlagForFilters(filters); assertEquals( String.format( - " %s=%s", - GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "filter1\\|filter2"), + " %s=%s", GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "filter1|filter2"), filterFlag); } @@ -366,8 +366,7 @@ public class GoogleBenchmarkTestTest extends TestCase { String filterFlag = mGoogleBenchmarkTest.getFilterFlagForTests(tests); assertEquals( String.format( - " %s=%s", - GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "^test1$\\|^test2$"), + " %s=%s", GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "^test1$|^test2$"), filterFlag); } @@ -407,7 +406,10 @@ public class GoogleBenchmarkTestTest extends TestCase { String incFilterFlag = mGoogleBenchmarkTest.getFilterFlagForFilters( mGoogleBenchmarkTest.getIncludeFilters()); - EasyMock.expect(mMockITestDevice.executeShellCommand(EasyMock.contains(incFilterFlag))) + EasyMock.expect( + mMockITestDevice.executeShellCommand( + EasyMock.contains( + StringEscapeUtils.escapeShell(incFilterFlag)))) .andReturn(incTests); } else { EasyMock.expect( @@ -422,14 +424,17 @@ public class GoogleBenchmarkTestTest extends TestCase { String excFilterFlag = mGoogleBenchmarkTest.getFilterFlagForFilters( mGoogleBenchmarkTest.getExcludeFilters()); - EasyMock.expect(mMockITestDevice.executeShellCommand(EasyMock.contains(excFilterFlag))) + EasyMock.expect( + mMockITestDevice.executeShellCommand( + EasyMock.contains( + StringEscapeUtils.escapeShell(excFilterFlag)))) .andReturn(excTests); } if (filteredTests != null && filteredTests.size() > 0) { // Runningt filtered tests String testFilterFlag = mGoogleBenchmarkTest.getFilterFlagForTests(filteredTests); mMockITestDevice.executeShellCommand( - EasyMock.contains(testFilterFlag), + EasyMock.contains(StringEscapeUtils.escapeShell(testFilterFlag)), EasyMock.same(mMockReceiver), EasyMock.anyLong(), (TimeUnit) EasyMock.anyObject(), diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java index d9e1e3e81..02359c967 100644 --- a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java +++ b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java @@ -16,6 +16,8 @@ package com.android.tradefed.testtype; +import static com.android.tradefed.testtype.InstrumentationTest.RUN_TESTS_AS_USER_KEY; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; @@ -28,6 +30,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -205,6 +208,27 @@ public class InstrumentationTestTest { } @Test + public void testRun_nullTestInfo() throws Exception { + mInstrumentationTest.run(/* testInfo= */ null, mMockListener); + + verify(mMockTestDevice, atLeastOnce()) + .runInstrumentationTests( + any(IRemoteAndroidTestRunner.class), any(ITestInvocationListener.class)); + } + + @Test + public void testRun_runTestsAsUser() throws DeviceNotAvailableException { + mTestInfo.properties().put(RUN_TESTS_AS_USER_KEY, "10"); + mInstrumentationTest.run(mTestInfo, mMockListener); + + verify(mMockTestDevice, atLeastOnce()) + .runInstrumentationTestsAsUser( + any(IRemoteAndroidTestRunner.class), + eq(10), + any(ITestInvocationListener.class)); + } + + @Test public void testRun_bothAbi() throws DeviceNotAvailableException { mInstrumentationTest.setAbi(mock(IAbi.class)); mInstrumentationTest.setForceAbi("test"); diff --git a/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java b/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java index de69a0519..27e793bf7 100644 --- a/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java +++ b/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java @@ -28,6 +28,9 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -78,6 +81,7 @@ public class MoblyBinaryHostTestTest { private File mMoblyTestDir; private File mMoblyBinary; // used by python-binaries option private File mMoblyBinary2; // used by par-file-name option + private File mVenvDir; private DeviceBuildInfo mMockBuildInfo; @Before @@ -87,6 +91,10 @@ public class MoblyBinaryHostTestTest { mMockRunUtil = Mockito.mock(IRunUtil.class); mMockBuildInfo = Mockito.mock(DeviceBuildInfo.class); mSpyTest.setDevice(mMockDevice); + + mVenvDir = FileUtil.createTempDir("venv"); + new File(mVenvDir, "bin").mkdir(); + Mockito.doReturn(mMockRunUtil).when(mSpyTest).getRunUtil(); Mockito.doReturn(DEFAULT_TIME_OUT).when(mSpyTest).getTestTimeout(); Mockito.doReturn("not_adb").when(mSpyTest).getAdbPath(); @@ -99,6 +107,7 @@ public class MoblyBinaryHostTestTest { @After public void tearDown() throws Exception { FileUtil.recursiveDelete(mMoblyTestDir); + FileUtil.recursiveDelete(mVenvDir); } @Test @@ -216,6 +225,71 @@ public class MoblyBinaryHostTestTest { } @Test + public void testRun_shouldActivateVenvAndCleanUp_whenVenvIsSet() throws Exception { + Mockito.when(mMockBuildInfo.getFile(eq("VIRTUAL_ENV"))).thenReturn(mVenvDir); + OptionSetter setter = new OptionSetter(mSpyTest); + setter.setOptionValue("python-binaries", mMoblyBinary.getAbsolutePath()); + File testResult = new File(mSpyTest.getLogDirAbsolutePath(), TEST_RESULT_FILE_NAME); + Mockito.when( + mMockRunUtil.runTimedCmd( + anyLong(), + anyString(), + eq("--"), + contains("--device_serial="), + contains("--log_path="))) + .thenAnswer( + new Answer<CommandResult>() { + @Override + public CommandResult answer(InvocationOnMock invocation) + throws Throwable { + FileUtils.createFile(testResult, ""); + FileUtils.createFile( + new File(mSpyTest.getLogDirAbsolutePath(), "log"), + "log content"); + return new CommandResult(CommandStatus.SUCCESS); + } + }); + CommandResult result = new CommandResult(CommandStatus.SUCCESS); + result.setStdout( + "Name: pip\nLocation: " + + new File(mVenvDir.getAbsolutePath(), "lib/python3.8/site-packages")); + Mockito.when(mMockRunUtil.runTimedCmd(anyLong(), anyString(), eq("show"), eq("pip"))) + .thenReturn(result); + + mSpyTest.run(Mockito.mock(ITestInvocationListener.class)); + + verify(mSpyTest.getRunUtil(), times(1)) + .setEnvVariable(eq("VIRTUAL_ENV"), eq(mVenvDir.getAbsolutePath())); + assertFalse(mVenvDir.exists()); + } + + @Test + public void testRun_shouldNotActivateVenv_whenVenvIsNotSet() throws Exception { + FileUtil.recursiveDelete(mVenvDir); + OptionSetter setter = new OptionSetter(mSpyTest); + setter.setOptionValue("python-binaries", mMoblyBinary.getAbsolutePath()); + File testResult = new File(mSpyTest.getLogDirAbsolutePath(), TEST_RESULT_FILE_NAME); + Mockito.when(mMockRunUtil.runTimedCmd(anyLong(), any())) + .thenAnswer( + new Answer<CommandResult>() { + @Override + public CommandResult answer(InvocationOnMock invocation) + throws Throwable { + FileUtils.createFile(testResult, ""); + FileUtils.createFile( + new File(mSpyTest.getLogDirAbsolutePath(), "log"), + "log content"); + return new CommandResult(CommandStatus.SUCCESS); + } + }); + + mSpyTest.run(Mockito.mock(ITestInvocationListener.class)); + + verify(mSpyTest.getRunUtil(), never()) + .setEnvVariable(eq("VIRTUAL_ENV"), eq(mVenvDir.getAbsolutePath())); + } + + @Test public void testBuildCommandLineArrayWithOutConfig() throws Exception { Mockito.doNothing().when(mSpyTest).reportLogs(any(), any()); Mockito.doReturn(DEVICE_SERIAL).when(mMockDevice).getSerialNumber(); @@ -262,7 +336,11 @@ public class MoblyBinaryHostTestTest { Mockito.doNothing().when(mSpyTest).reportLogs(any(), any()); mMockSummaryInputStream = Mockito.mock(InputStream.class); mMockParser = Mockito.mock(MoblyYamlResultParser.class); - mSpyTest.processYamlTestResults(mMockSummaryInputStream, mMockParser); + mSpyTest.processYamlTestResults( + mMockSummaryInputStream, + mMockParser, + Mockito.mock(ITestInvocationListener.class), + "runName"); verify(mMockParser, times(1)).parse(mMockSummaryInputStream); } diff --git a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java index 3cee111d1..105046093 100644 --- a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java +++ b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java @@ -26,6 +26,7 @@ import com.android.ddmlib.testrunner.TestResult.TestStatus; import com.android.tradefed.config.IConfiguration; import com.android.tradefed.config.Option; import com.android.tradefed.config.OptionSetter; +import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.DeviceUnresponsiveException; import com.android.tradefed.device.ITestDevice; @@ -263,13 +264,22 @@ public class GranularRetriableTestWrapperTest { private GranularRetriableTestWrapper createGranularTestWrapper( IRemoteTest test, int maxRunCount) throws Exception { - return createGranularTestWrapper(test, maxRunCount, new ArrayList<>()); + return createGranularTestWrapper(test, maxRunCount, new ArrayList<>(), null); } private GranularRetriableTestWrapper createGranularTestWrapper( IRemoteTest test, int maxRunCount, List<IMetricCollector> collectors) throws Exception { + return createGranularTestWrapper(test, maxRunCount, collectors, null); + } + + private GranularRetriableTestWrapper createGranularTestWrapper( + IRemoteTest test, + int maxRunCount, + List<IMetricCollector> collectors, + ModuleDefinition module) + throws Exception { GranularRetriableTestWrapper granularTestWrapper = - new GranularRetriableTestWrapper(test, null, null, null, maxRunCount); + new GranularRetriableTestWrapper(test, module, null, null, null, maxRunCount); granularTestWrapper.setModuleId("test module"); granularTestWrapper.setMarkTestsSkipped(false); granularTestWrapper.setMetricCollectors(collectors); @@ -904,6 +914,101 @@ public class GranularRetriableTestWrapperTest { EasyMock.verify(mMockDevice, mMockDevice2); } + /** Test to reset multi-devices at the last intra-module retry. */ + @Test + public void testIntraModuleRun_resetMultiDevicesAtLastIntraModuleRetry() throws Exception { + IRetryDecision decision = new BaseRetryDecision(); + OptionSetter setter = new OptionSetter(decision); + setter.setOptionValue("reset-at-last-retry", "true"); + setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE"); + setter.setOptionValue("max-testcase-run-count", Integer.toString(3)); + decision.setInvocationContext(mModuleInvocationContext); + FakeTest test = new FakeTest(); + test.setRunFailure("I failed!"); + ITestDevice noneAVDDevice = EasyMock.createMock(ITestDevice.class); + + RemoteAndroidVirtualDevice avdDevice = Mockito.mock(RemoteAndroidVirtualDevice.class); + Mockito.when(avdDevice.powerwashGce()).thenReturn(true); + + ModuleDefinition module = Mockito.mock(ModuleDefinition.class); + // Should call suite level preparers. + Mockito.when(module.runPreparation(true)).thenReturn(null); + + mModuleInvocationContext.addAllocatedDevice("default-device1", noneAVDDevice); + mModuleInvocationContext.addAllocatedDevice("default-device2", avdDevice); + GranularRetriableTestWrapper granularTestWrapper = + createGranularTestWrapper(test, 3, new ArrayList<>(), module); + granularTestWrapper.setRetryDecision(decision); + EasyMock.expect(noneAVDDevice.getIDevice()) + .andStubReturn(EasyMock.createMock(IDevice.class)); + EasyMock.expect(noneAVDDevice.getSerialNumber()).andStubReturn("device-1"); + + EasyMock.replay(noneAVDDevice); + granularTestWrapper.run(mModuleInfo, new CollectingTestListener()); + EasyMock.verify(noneAVDDevice); + } + + /** Test to reset device at the last intra-module retry failed due to preparer failure. */ + @Test + public void testIntraModuleRun_resetFailed_preparerFailure() throws Exception { + IRetryDecision decision = new BaseRetryDecision(); + OptionSetter setter = new OptionSetter(decision); + setter.setOptionValue("reset-at-last-retry", "true"); + setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE"); + setter.setOptionValue("max-testcase-run-count", Integer.toString(3)); + decision.setInvocationContext(mModuleInvocationContext); + FakeTest test = new FakeTest(); + test.setRunFailure("I failed!"); + + RemoteAndroidVirtualDevice avdDevice = Mockito.mock(RemoteAndroidVirtualDevice.class); + Mockito.when(avdDevice.powerwashGce()).thenReturn(true); + + ModuleDefinition module = Mockito.mock(ModuleDefinition.class); + // Suite level preparers failed. + Mockito.when(module.runPreparation(true)).thenReturn(new RuntimeException()); + + mModuleInvocationContext.addAllocatedDevice("default-device2", avdDevice); + GranularRetriableTestWrapper granularTestWrapper = + createGranularTestWrapper(test, 3, new ArrayList<>(), module); + granularTestWrapper.setRetryDecision(decision); + + try { + granularTestWrapper.run(mModuleInfo, new CollectingTestListener()); + fail("Exception should be raised when reset is failed."); + } catch (DeviceNotAvailableException e) { + assertTrue(e.getMessage().startsWith("Failed to reset devices before retry: ")); + } + } + + /** Test to reset device at the last intra-module retry failed due to reset failure. */ + @Test + public void testIntraModuleRun_resetFailed_powerwashFailure() throws Exception { + IRetryDecision decision = new BaseRetryDecision(); + OptionSetter setter = new OptionSetter(decision); + setter.setOptionValue("reset-at-last-retry", "true"); + setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE"); + setter.setOptionValue("max-testcase-run-count", Integer.toString(3)); + decision.setInvocationContext(mModuleInvocationContext); + FakeTest test = new FakeTest(); + test.setRunFailure("I failed!"); + + RemoteAndroidVirtualDevice device = Mockito.mock(RemoteAndroidVirtualDevice.class); + Mockito.when(device.powerwashGce()).thenReturn(false); + Mockito.when(device.getSerialNumber()).thenReturn("device1"); + + test.setDevice(device); + mModuleInvocationContext.addAllocatedDevice("default-device1", device); + GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3); + granularTestWrapper.setRetryDecision(decision); + + try { + granularTestWrapper.run(mModuleInfo, new CollectingTestListener()); + fail("Exception should be raised when reset is failed."); + } catch (DeviceNotAvailableException e) { + assertEquals("Failed to powerwash device: device1", e.getMessage()); + } + } + /** Collector that track if it was called or not */ public static class CalledMetricCollector extends BaseDeviceMetricCollector { diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java index 2b3db6b6f..5d43bb2b1 100644 --- a/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java +++ b/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java @@ -37,8 +37,11 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** Unit tests for {@link ModuleSplitter}. */ @RunWith(JUnit4.class) @@ -46,6 +49,8 @@ public class ModuleSplitterTest { private static final String DEFAULT_DEVICE = ConfigurationDef.DEFAULT_DEVICE_NAME; private TestInformation mTestInfo = TestInformation.newBuilder().build(); + private Map<String, List<ITargetPreparer>> mSuitePreparersPerDevice = + new HashMap<String, List<ITargetPreparer>>(); /** * Tests that {@link ModuleSplitter#splitConfiguration(TestInformation, LinkedHashMap, int, @@ -69,7 +74,8 @@ public class ModuleSplitterTest { setter.setOptionValue("not-shardable", "true"); runConfig.put("module1", config); List<ModuleDefinition> res = - ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true); + ModuleSplitter.splitConfiguration( + mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true); // matching 1 for 1, config to ModuleDefinition since not shardable assertEquals(1, res.size()); // The original target preparer is changed since we split multiple <test> tags. @@ -105,7 +111,8 @@ public class ModuleSplitterTest { setter.setOptionValue("not-strict-shardable", "true"); runConfig.put("module1", config); List<ModuleDefinition> res = - ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true); + ModuleSplitter.splitConfiguration( + mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true); // We are sharding since even if we are not-strict-shardable, we are in dynamic context assertEquals(10, res.size()); // The original target preparer is changed since we split multiple <test> tags. @@ -140,7 +147,8 @@ public class ModuleSplitterTest { setter.setOptionValue("not-strict-shardable", "true"); runConfig.put("module1", config); List<ModuleDefinition> res = - ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, false, true); + ModuleSplitter.splitConfiguration( + mTestInfo, runConfig, mSuitePreparersPerDevice, 5, false, true); // matching 1 for 1, config to ModuleDefinition since not shardable assertEquals(1, res.size()); // The original target preparer is changed since we split multiple <test> tags. @@ -169,7 +177,8 @@ public class ModuleSplitterTest { runConfig.put("module1", config); List<ModuleDefinition> res = - ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, false); + ModuleSplitter.splitConfiguration( + mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, false); // matching 1 for 1, config to ModuleDefinition since no intra-module sharding assertEquals(1, res.size()); // The original target preparer is changed since we split multiple <test> tags. @@ -206,7 +215,8 @@ public class ModuleSplitterTest { runConfig.put("module1", config); List<ModuleDefinition> res = - ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true); + ModuleSplitter.splitConfiguration( + mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true); // matching 1 for 1, config to ModuleDefinition since not shardable assertEquals(1, res.size()); // The original target preparer is not there, it has been copied @@ -239,7 +249,8 @@ public class ModuleSplitterTest { runConfig.put("module1", config); List<ModuleDefinition> res = - ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true); + ModuleSplitter.splitConfiguration( + mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true); // matching 1 for 1, config to ModuleDefinition since did not shard assertEquals(1, res.size()); // The original target preparer is not there, it has been copied @@ -271,14 +282,25 @@ public class ModuleSplitterTest { setter.setOptionValue("num-shards", "6"); config.setTest(test); + Map<String, List<ITargetPreparer>> suitePreparers = + new HashMap<String, List<ITargetPreparer>>(); + ITargetPreparer preparer1 = new StubTargetPreparer(); + ITargetPreparer preparer2 = new StubTargetPreparer(); + List<ITargetPreparer> preparers = Arrays.asList(preparer1, preparer2); + suitePreparers.put(DEFAULT_DEVICE, preparers); + runConfig.put("module1", config); List<ModuleDefinition> res = - ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true); + ModuleSplitter.splitConfiguration( + mTestInfo, runConfig, suitePreparers, 5, true, true); // matching 1 for 10 since tests sharding in 5 units times 2. assertEquals(10, res.size()); // The original IRemoteTest does not exists anymore, new IRemoteTests have been created. for (ModuleDefinition m : res) { assertNotSame(test, m.getTests().get(0)); + assertEquals(2, m.getSuitePreparerForDevice(DEFAULT_DEVICE).size()); + assertNotSame(preparer1, m.getSuitePreparerForDevice(DEFAULT_DEVICE).get(0)); + assertNotSame(preparer1, m.getSuitePreparerForDevice(DEFAULT_DEVICE).get(1)); } assertTrue(config.getTests().isEmpty()); } @@ -302,7 +324,8 @@ public class ModuleSplitterTest { runConfig.put("module1", config); List<ModuleDefinition> res = - ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, false, true); + ModuleSplitter.splitConfiguration( + mTestInfo, runConfig, mSuitePreparersPerDevice, 5, false, true); // matching 1 for 6 since tests sharding in 6 tests. assertEquals(6, res.size()); // The original IRemoteTest does not exists anymore, new IRemoteTests have been created. diff --git a/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java b/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java index 07e080718..81fa44f9e 100644 --- a/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java +++ b/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertTrue; import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey; import com.android.tradefed.build.IDeviceBuildInfo; +import com.android.tradefed.config.Configuration; import com.android.tradefed.config.ConfigurationDef; import com.android.tradefed.config.ConfigurationException; import com.android.tradefed.config.ConfigurationFactory; @@ -87,6 +88,7 @@ public class TestMappingSuiteRunnerTest { private IDeviceBuildInfo mBuildInfo; private ITestDevice mMockDevice; private TestInformation mTestInfo; + private IConfiguration mStubMainConfiguration; private static final String TEST_MAINLINE_CONFIG = "<configuration description=\"Runs a stub tests part of some suite\">\n" @@ -115,6 +117,8 @@ public class TestMappingSuiteRunnerTest { mMainlineRunner = new FakeMainlineTMSR(); mMainlineRunner.setBuild(mBuildInfo); mMainlineRunner.setDevice(mMockDevice); + mStubMainConfiguration = new Configuration("stub", "stub"); + mMainlineRunner.setConfiguration(mStubMainConfiguration); mMainlineOptionSetter = new OptionSetter(mMainlineRunner); IInvocationContext context = new InvocationContext(); diff --git a/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java b/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java index 11dee185d..4a5d70763 100644 --- a/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java +++ b/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java @@ -62,7 +62,7 @@ public final class MainlineModuleHandlerTest { public void testApplySetup() { EasyMock.expect(mMockBuildInfo.getBuildBranch()).andStubReturn("branch"); EasyMock.replay(mMockBuildInfo); - mHandler = new MainlineModuleHandler("mod1.apk", mAbi, mContext); + mHandler = new MainlineModuleHandler("mod1.apk", mAbi, mContext, false); mHandler.applySetup(mConfig); assertTrue(mConfig.getTargetPreparers().get(0) instanceof InstallApexModuleTargetPreparer); InstallApexModuleTargetPreparer preparer = @@ -77,7 +77,7 @@ public final class MainlineModuleHandlerTest { public void testApplySetup_MultipleMainlineModules() { EasyMock.expect(mMockBuildInfo.getBuildBranch()).andStubReturn("branch"); EasyMock.replay(mMockBuildInfo); - mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext); + mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext, false); mHandler.applySetup(mConfig); assertTrue(mConfig.getTargetPreparers().get(0) instanceof InstallApexModuleTargetPreparer); InstallApexModuleTargetPreparer preparer = @@ -97,7 +97,7 @@ public final class MainlineModuleHandlerTest { try { EasyMock.expect(mMockBuildInfo.getBuildBranch()).andStubReturn(null); EasyMock.replay(mMockBuildInfo); - mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext); + mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext, false); fail("Should have thrown an exception."); } catch (IllegalArgumentException expected) { // expected diff --git a/tests/src/com/android/tradefed/util/AaptParserTest.java b/tests/src/com/android/tradefed/util/AaptParserTest.java index 65bf70dd5..93caecff8 100644 --- a/tests/src/com/android/tradefed/util/AaptParserTest.java +++ b/tests/src/com/android/tradefed/util/AaptParserTest.java @@ -168,6 +168,34 @@ public class AaptParserTest extends TestCase { assertTrue(p.isRequestingLegacyStorage()); } + public void testParseXmlTreeForAapt2_withRequestLegacyFlagTrue() { + AaptParser p = new AaptParser(); + p.parseXmlTree( + "N: android=http://schemas.android.com/apk/res/android\n" + + " E: manifest (line=2)\n" + + " A: http://schemas.android.com/apk/res/android:versionCode(0x0101021b)=(type 0x10)0x1d\n" + + " A: http://schemas.android.com/apk/res/android:versionName(0x0101021c)=\"R\" (Raw: \"R\")\n" + + " A: http://schemas.android.com/apk/res/android:compileSdkVersion(0x01010572)=(type 0x10)0x1d\n" + + " A: http://schemas.android.com/apk/res/android:compileSdkVersionCodename(0x01010573)=\"R\" (Raw: " + + "\"R\")\n" + + " A: package=\"com.android.foo\" (Raw: \"com.android.foo\")\n" + + " A: platformBuildVersionCode=(type 0x10)0x1d\n" + + " A: platformBuildVersionName=\"R\" (Raw: \"R\")\n" + + " E: uses-sdk (line=5)\n" + + " A: http://schemas.android.com/apk/res/android:minSdkVersion(0x0101020c)=(type 0x10)0x1c\n" + + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=\"R\" (Raw: \"R\")\n" + + " E: application (line=12)\n" + + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=(type 0x10)0x1e\n" + + " A: http://schemas.android.com/apk/res/android:supportsRtl(0x010103af)=(type 0x12)0xffffffff\n" + + " A: http://schemas.android.com/apk/res/android:extractNativeLibs(0x010104ea)=(type 0x12)0xffffffff\n" + + " A: http://schemas.android.com/apk/res/android:appComponentFactory(0x0101057a)=\"androidx.core.app" + + ".CoreComponentFactory\" (Raw: \"androidx.core.app" + + ".CoreComponentFactory\")\n" + + " A: http://schemas.android.com/apk/res/android:requestLegacyExternalStorage(0x01010603)=(type 0x12)" + + "0xffffffff\n"); + assertTrue(p.isRequestingLegacyStorage()); + } + public void testParseXmlTree_withRequestLegacyFlagFalse() { AaptParser p = new AaptParser(); p.parseXmlTree( @@ -196,6 +224,34 @@ public class AaptParserTest extends TestCase { assertFalse(p.isRequestingLegacyStorage()); } + public void testParseXmlTreeForAapt2_withRequestLegacyFlagFalse() { + AaptParser p = new AaptParser(); + p.parseXmlTree( + "N: android=http://schemas.android.com/apk/res/android\n" + + " E: manifest (line=2)\n" + + " A: http://schemas.android.com/apk/res/android:versionCode(0x0101021b)=(type 0x10)0x1d\n" + + " A: http://schemas.android.com/apk/res/android:versionName(0x0101021c)=\"R\" (Raw: \"R\")\n" + + " A: http://schemas.android.com/apk/res/android:compileSdkVersion(0x01010572)=(type 0x10)0x1d\n" + + " A: http://schemas.android.com/apk/res/android:compileSdkVersionCodename(0x01010573)=\"R\" (Raw: " + + "\"R\")\n" + + " A: package=\"com.android.foo\" (Raw: \"com.android.foo\")\n" + + " A: platformBuildVersionCode=(type 0x10)0x1d\n" + + " A: platformBuildVersionName=\"R\" (Raw: \"R\")\n" + + " E: uses-sdk (line=5)\n" + + " A: http://schemas.android.com/apk/res/android:minSdkVersion(0x0101020c)=(type 0x10)0x1c\n" + + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=\"R\" (Raw: \"R\")\n" + + " E: application (line=12)\n" + + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=(type 0x10)0x1e\n" + + " A: http://schemas.android.com/apk/res/android:supportsRtl(0x010103af)=(type 0x12)0xffffffff\n" + + " A: http://schemas.android.com/apk/res/android:extractNativeLibs(0x010104ea)=(type 0x12)0xffffffff\n" + + " A: http://schemas.android.com/apk/res/android:appComponentFactory(0x0101057a)=\"androidx.core.app" + + ".CoreComponentFactory\" (Raw: \"androidx.core.app" + + ".CoreComponentFactory\")\n" + + " A: http://schemas.android.com/apk/res/android:requestLegacyExternalStorage(0x01010603)=(type 0x12)" + + "0x0\n"); + assertFalse(p.isRequestingLegacyStorage()); + } + public void testParseXmlTree_withoutRequestLegacyFlag() { AaptParser p = new AaptParser(); p.parseXmlTree( @@ -221,7 +277,32 @@ public class AaptParserTest extends TestCase { assertFalse(p.isRequestingLegacyStorage()); } - public void testParseXmlTree_withUsesPermissionManageExternalStorage() { + public void testParseXmlTreeForAapt2_withoutRequestLegacyFlag() { + AaptParser p = new AaptParser(); + p.parseXmlTree( + "N: android=http://schemas.android.com/apk/res/android\n" + + " E: manifest (line=2)\n" + + " A: http://schemas.android.com/apk/res/androidandroid:versionCode(0x0101021b)=(type 0x10)0x1d\n" + + " A: http://schemas.android.com/apk/res/androidandroid:versionName(0x0101021c)=\"R\" (Raw: \"R\")\n" + + " A: http://schemas.android.com/apk/res/androidandroid:compileSdkVersion(0x01010572)=(type 0x10)0x1d\n" + + " A: http://schemas.android.com/apk/res/androidandroid:compileSdkVersionCodename(0x01010573)=\"R\" (Raw: " + + "\"R\")\n" + + " A: package=\"com.android.foo\" (Raw: \"com.android.foo\")\n" + + " A: platformBuildVersionCode=(type 0x10)0x1d\n" + + " A: platformBuildVersionName=\"R\" (Raw: \"R\")\n" + + " E: uses-sdk (line=5)\n" + + " A: http://schemas.android.com/apk/res/androidandroid:minSdkVersion(0x0101020c)=(type 0x10)0x1c\n" + + " A: http://schemas.android.com/apk/res/androidandroid:targetSdkVersion(0x01010270)=\"R\" (Raw: \"R\")\n" + + " E: application (line=12)\n" + + " A: http://schemas.android.com/apk/res/androidandroid:targetSdkVersion(0x01010270)=(type 0x10)0x1e\n" + + " A: http://schemas.android.com/apk/res/androidandroid:supportsRtl(0x010103af)=(type 0x12)0xffffffff\n" + + " A: http://schemas.android.com/apk/res/androidandroid:extractNativeLibs(0x010104ea)=(type 0x12)0xffffffff\n" + + " A: http://schemas.android.com/apk/res/androidandroid:appComponentFactory(0x0101057a)=\"androidx.core.app" + + ".CoreComponentFactory\" (Raw: \"androidx.core.app"); + assertFalse(p.isRequestingLegacyStorage()); + } + + public void testParse_withUsesPermissionManageExternalStorage() { AaptParser p = new AaptParser(); p.parse( "package: name='com.android.foo' versionCode='217173' versionName='1.7173' " @@ -234,7 +315,7 @@ public class AaptParserTest extends TestCase { assertTrue(p.isUsingPermissionManageExternalStorage()); } - public void testParseXmlTree_withoutUsesPermissionManageExternalStorage() { + public void testParse_withoutUsesPermissionManageExternalStorage() { AaptParser p = new AaptParser(); p.parse( "package: name='com.android.foo' versionCode='217173' versionName='1.7173' " diff --git a/tests/src/com/android/tradefed/util/ProtoUtilTest.java b/tests/src/com/android/tradefed/util/ProtoUtilTest.java new file mode 100644 index 000000000..2e42eee55 --- /dev/null +++ b/tests/src/com/android/tradefed/util/ProtoUtilTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tradefed.util; + +import static org.junit.Assert.assertArrayEquals; + +import com.android.tradefed.util.test.ProtoUtilTestProto.TestMessage; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** Unit tests for {@link ProtoUtil} */ +@RunWith(Parameterized.class) +public class ProtoUtilTest { + @Parameter(0) + public String mTestName; // Unused, for identifying tests only. + + @Parameter(1) + public TestMessage mMessage; + + @Parameter(2) + public List<String> mReferences; + + @Parameter(3) + public List<String> mExpectedResults; + + @Parameters(name = "{0}#{index}") + public static Iterable<Object[]> data() { + List<Object[]> parameters = new ArrayList<>(); + parameters.add( + new Object[] { + "returnsMessageAsStringForEmptyReference", + TestMessage.newBuilder().setIntField(7).build(), + new ArrayList<String>(), + Arrays.asList(TestMessage.newBuilder().setIntField(7).build().toString()) + }); + parameters.add( + new Object[] { + "singleLevel", + TestMessage.newBuilder().setIntField(7).build(), + Arrays.asList("int_field"), + Arrays.asList("7") + }); + parameters.add( + new Object[] { + "singleLevel", + TestMessage.newBuilder().setStringField("string").build(), + Arrays.asList("string_field"), + Arrays.asList("string") + }); + parameters.add( + new Object[] { + "singleLevel", + TestMessage.newBuilder() + .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7)) + .build(), + Arrays.asList("message_field"), + Arrays.asList(TestMessage.SubMessage.newBuilder().setIntField(7).toString()) + }); + parameters.add( + new Object[] { + "singleLevelRepeated", + TestMessage.newBuilder() + .addAllRepeatedStringField(Arrays.asList("string1", "string2")) + .build(), + Arrays.asList("repeated_string_field"), + Arrays.asList("string1", "string2") + }); + parameters.add( + new Object[] { + "multiLevel", + TestMessage.newBuilder() + .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7)) + .build(), + Arrays.asList("message_field", "int_field"), + Arrays.asList("7") + }); + parameters.add( + new Object[] { + "multiLevelRepeated", + TestMessage.newBuilder() + .setMessageField( + TestMessage.SubMessage.newBuilder() + .addAllRepeatedStringField( + Arrays.asList("string1", "string2"))) + .build(), + Arrays.asList("message_field", "repeated_string_field"), + Arrays.asList("string1", "string2") + }); + parameters.add( + new Object[] { + "multiLevelRepeated", + TestMessage.newBuilder() + .addAllRepeatedMessageField( + Arrays.asList( + TestMessage.SubMessage.newBuilder() + .addAllRepeatedStringField( + Arrays.asList("string1", "string2")) + .build(), + TestMessage.SubMessage.newBuilder() + .addAllRepeatedStringField( + Arrays.asList("string3", "string4")) + .build())) + .build(), + Arrays.asList("repeated_message_field", "repeated_string_field"), + Arrays.asList("string1", "string2", "string3", "string4") + }); + parameters.add( + new Object[] { + "oneofSingleLevel", + TestMessage.newBuilder().setOneofStringField("string").build(), + Arrays.asList("oneof_string_field"), + Arrays.asList("string") + }); + parameters.add( + new Object[] { + "oneofMultiLevel", + TestMessage.newBuilder() + .setOneofMessageField( + TestMessage.SubMessage.newBuilder() + .addAllRepeatedStringField( + Arrays.asList("string1", "string2"))) + .build(), + Arrays.asList("oneof_message_field", "repeated_string_field"), + Arrays.asList("string1", "string2") + }); + parameters.add( + new Object[] { + "returnsEmptyForNonExistentFieldReference", + TestMessage.newBuilder().setStringField("string").build(), + Arrays.asList("not_a_field"), + new ArrayList<String>() + }); + parameters.add( + new Object[] { + "returnsEmptyForNonExistentFieldReference", + TestMessage.newBuilder() + .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7)) + .build(), + Arrays.asList("message_field", "not_a_field"), + new ArrayList<String>() + }); + parameters.add( + new Object[] { + "returnsEmptyForNonExistentFieldReference", + TestMessage.newBuilder() + .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7)) + .build(), + Arrays.asList("message_field", "int_field", "not_a_field"), + new ArrayList<String>() + }); + return parameters; + } + + @Test + public void testParsing() { + assertArrayEquals( + mExpectedResults.toArray(), + ProtoUtil.getNestedFieldFromMessageAsStrings(mMessage, mReferences).toArray()); + } +} diff --git a/tests/src/com/android/tradefed/util/PythonVirtualenvHelperTest.java b/tests/src/com/android/tradefed/util/PythonVirtualenvHelperTest.java new file mode 100644 index 000000000..aeb1c2466 --- /dev/null +++ b/tests/src/com/android/tradefed/util/PythonVirtualenvHelperTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.util; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.*; + +import com.google.common.base.Throwables; + +import org.junit.After; +import org.junit.Test; + +import java.io.File; +import java.nio.file.Paths; + +public class PythonVirtualenvHelperTest { + + private File mVenvDir; + + @After + public void tearDown() throws Exception { + FileUtil.recursiveDelete(mVenvDir); + } + + @Test + public void testActivate_shouldThrowNPE_whenVirtualenvPathIsNull() throws Exception { + String nullVirtualenvPath = null; + IRunUtil runUtil = mock(RunUtil.class); + + try { + PythonVirtualenvHelper.activate(runUtil, nullVirtualenvPath); + fail("Should have thrown an exception"); + } catch (NullPointerException e) { + assertThat( + String.format( + "An unexpected exception was thrown, full stack trace: %s", + Throwables.getStackTraceAsString(e)), + e.getMessage(), + containsString("Path to the Python virtual environment should not be null")); + } + } + + @Test + public void testActivate_whenVirtualenvPathIsInvalid() throws Exception { + mVenvDir = FileUtil.createTempDir("venv"); + mVenvDir.delete(); + IRunUtil runUtil = mock(RunUtil.class); + + try { + PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath()); + fail("Should have thrown an exception"); + } catch (RuntimeException e) { + assertThat( + String.format( + "An unexpected exception was thrown, full stack trace: %s", + Throwables.getStackTraceAsString(e)), + e.getMessage(), + containsString("Invalid python virtualenv path")); + } + } + + @Test + public void testActivate_whenPythonBinNotFound() throws Exception { + mVenvDir = FileUtil.createTempDir("venv"); + IRunUtil runUtil = mock(RunUtil.class); + + try { + PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath()); + fail("Should have thrown an exception"); + } catch (RuntimeException e) { + assertThat( + String.format( + "An unexpected exception was thrown, full stack trace: %s", + Throwables.getStackTraceAsString(e)), + e.getMessage(), + containsString("Invalid python virtualenv path")); + } + } + + @Test + public void testActivate_success() throws Exception { + mVenvDir = FileUtil.createTempDir("venv"); + File pythonBin = new File(mVenvDir, "bin"); + pythonBin.mkdir(); + IRunUtil runUtil = mock(RunUtil.class); + CommandResult result = new CommandResult(CommandStatus.SUCCESS); + result.setStdout( + "Name: pip\nLocation: " + + Paths.get(mVenvDir.getAbsolutePath(), "lib/python3.8/site-packages")); + when(runUtil.runTimedCmd(anyLong(), anyString(), eq("show"), eq("pip"))).thenReturn(result); + + PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath()); + + verify(runUtil) + .setEnvVariable("PATH", pythonBin.getAbsolutePath() + ":" + System.getenv("PATH")); + verify(runUtil).setEnvVariable("VIRTUAL_ENV", mVenvDir.getAbsolutePath()); + verify(runUtil) + .setEnvVariable( + "PYTHONPATH", + new File(mVenvDir, "lib/python3.8/site-packages").getAbsolutePath() + + ":" + + System.getenv("PYTHONPATH")); + verify(runUtil).unsetEnvVariable("PYTHONHOME"); + } + + @Test + public void testActivate_pipShowFails() throws Exception { + mVenvDir = FileUtil.createTempDir("venv"); + File pythonBin = new File(mVenvDir, "bin"); + pythonBin.mkdir(); + IRunUtil runUtil = mock(RunUtil.class); + when(runUtil.runTimedCmd(anyLong(), anyString(), eq("show"), eq("pip"))) + .thenReturn(new CommandResult()); + + try { + PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath()); + fail("Should have thrown an exception"); + } catch (RuntimeException e) { + assertThat( + String.format( + "An unexpected exception was thrown, full stack trace: %s", + Throwables.getStackTraceAsString(e)), + e.getMessage(), + containsString("pip3 show pip")); + } + } +} diff --git a/tests/src/com/android/tradefed/util/RemoteZipTest.java b/tests/src/com/android/tradefed/util/RemoteZipTest.java index 0176a0741..1d6056a16 100644 --- a/tests/src/com/android/tradefed/util/RemoteZipTest.java +++ b/tests/src/com/android/tradefed/util/RemoteZipTest.java @@ -103,7 +103,7 @@ public class RemoteZipTest { List<CentralDirectoryInfo> entries = remoteZip.getZipEntries(); - assertEquals(7, entries.size()); + assertEquals(8, entries.size()); assertTrue(mExpectedEntries.containsAll(entries)); } finally { FileUtil.recursiveDelete(destDir); @@ -122,7 +122,7 @@ public class RemoteZipTest { destDir = FileUtil.createTempDir("test"); RemoteZip remoteZip = new RemoteZip(REMOTE_FILE, mZipFileSize, mDownloader, true); List<CentralDirectoryInfo> entries = remoteZip.getZipEntries(); - assertEquals(7, entries.size()); + assertEquals(8, entries.size()); assertTrue(mExpectedEntries.containsAll(entries)); } finally { FileUtil.recursiveDelete(destDir); @@ -151,7 +151,7 @@ public class RemoteZipTest { targetFile = Paths.get(destDir.getPath(), "executable", "executable_file").toFile(); assertTrue(targetFile.exists()); // File not in the list is not unzipped. - targetFile = Paths.get(destDir.getPath(), "empty_file").toFile(); + targetFile = Paths.get(destDir.getPath(), "empty/empty_file").toFile(); assertFalse(targetFile.exists()); } finally { FileUtil.recursiveDelete(destDir); diff --git a/tests/src/com/android/tradefed/util/SparseImageUtilTest.java b/tests/src/com/android/tradefed/util/SparseImageUtilTest.java new file mode 100644 index 000000000..d8858e7ec --- /dev/null +++ b/tests/src/com/android/tradefed/util/SparseImageUtilTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tradefed.util; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** Unit tests for {@link SparseImageUtil} */ +@RunWith(JUnit4.class) +public class SparseImageUtilTest { + private File mSparseImageFile; + + @Before + public void setUp() throws IOException { + mSparseImageFile = FileUtil.createTempFile("sparse", ".img"); + try (FileOutputStream out = new FileOutputStream(mSparseImageFile)) { + out.write(getSparseImageData()); + } + } + + @After + public void tearDown() { + FileUtil.deleteFile(mSparseImageFile); + } + + /** Verify {@link com.android.tradefed.util.SparseImageUtil#isSparse}. */ + @Test + public void testIsSparse() { + Assert.assertTrue(SparseImageUtil.isSparse(mSparseImageFile)); + } + + /** Verify {@link com.android.tradefed.util.SparseImageUtil#unsparse}. */ + @Test + public void testUnsparse() throws IOException { + File unsparsedFile = FileUtil.createTempFile("unsparse", ".img"); + byte[] unsparsedData = null; + try { + SparseImageUtil.unsparse(mSparseImageFile, unsparsedFile); + try (FileInputStream in = new FileInputStream(unsparsedFile)) { + unsparsedData = StreamUtil.getByteArrayListFromStream(in).getContents(); + } + Assert.assertArrayEquals(getUnsparsedImageData(), unsparsedData); + } finally { + FileUtil.deleteFile(unsparsedFile); + } + } + + /** + * Returns some sparse data. + * + * @see https://android.googlesource.com/platform/system/core/+/master/libsparse/sparse_format.h + */ + private byte[] getSparseImageData() { + final int SPARSE_IMAGE_MAGIC = 0xED26FF3A; + ByteBuffer buffer = ByteBuffer.allocate(4096); + buffer.order(ByteOrder.LITTLE_ENDIAN); + // Header + buffer.putInt(SPARSE_IMAGE_MAGIC); + buffer.putShort((short) 1); + buffer.putShort((short) 0); + buffer.putShort((short) 28); + buffer.putShort((short) 12); + buffer.putInt(4); /* block size */ + buffer.putInt(512 + 256); /* total blocks */ + buffer.putInt(2); /* total chunks */ + buffer.putInt(0); /* ignore check sum */ + // RAW chunk, 2048 bytes of lorem ipsum + byte[] loremIpsum = getLoremIpsum(); + buffer.putShort((short) 0xCAC1); + buffer.putShort((short) 0); /* padding */ + buffer.putInt(loremIpsum.length / 4); /* data size in terms of number of blocks */ + buffer.putInt(12 + loremIpsum.length); /* header size + data size */ + buffer.put(loremIpsum); + // DONTCARE chunk, 1024 bytes of zeroes + byte[] zeroes = new byte[1024]; + buffer.putShort((short) 0xCAC3); + buffer.putShort((short) 0); /* padding */ + buffer.putInt(zeroes.length / 4); /* data size in terms of number of blocks */ + buffer.putInt(12 + zeroes.length); /* header size + data size */ + buffer.put(zeroes); + return Arrays.copyOf(buffer.array(), buffer.position()); + } + + private byte[] getUnsparsedImageData() { + byte[] loremIpsum = getLoremIpsum(); + // Pad lorem ipsum with 1024 bytes of zeroes + return Arrays.copyOf(loremIpsum, loremIpsum.length + 1024); + } + + /** Returns a chunk of text data. */ + private byte[] getLoremIpsum() { + final int dataLen = 2048; /* Must be a multiple of 4 */ + final String loremIpsumString = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" + + " incididunt ut labore et dolore magna aliqua. Enim neque volutpat ac" + + " tincidunt vitae semper quis lectus. Est pellentesque elit ullamcorper" + + " dignissim cras tincidunt lobortis feugiat vivamus. Vitae ultricies leo" + + " integer malesuada nunc vel. Ultrices tincidunt arcu non sodales neque" + + " sodales ut etiam sit. Arcu cursus vitae congue mauris rhoncus aenean." + + " Consectetur a erat nam at lectus urna duis convallis convallis. Suscipit" + + " tellus mauris a diam maecenas sed. At elementum eu facilisis sed odio." + + " Neque sodales ut etiam sit."; + final byte[] loremIpsumBytes = loremIpsumString.getBytes(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + while (buffer.size() < dataLen) { + buffer.write(loremIpsumBytes, 0, loremIpsumBytes.length); + } + return Arrays.copyOf(buffer.toByteArray(), dataLen); + } +} diff --git a/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java b/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java index 7a81c5ee2..f166b6a5a 100644 --- a/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java +++ b/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java @@ -88,4 +88,30 @@ public class StringEscapeUtilsTest { assertArrayEquals(new String[]{"foo", "bar bar"}, StringEscapeUtils.paramsToArgs(expected).toArray()); } + + /** + * Simple test that {@link StringEscapeUtils#escapeShell(String)} escapes the is greater than + * sign. + */ + @Test + public void testEscapesGreaterSigns() { + String escaped_str = StringEscapeUtils.escapeShell(">greater>signs"); + assertEquals("\\>greater\\>signs", escaped_str); + } + + /** + * Simple test that {@link StringEscapeUtils#escapeShell(String)} escapes the is less than sign. + */ + @Test + public void testEscapesLessSigns() { + String escaped_str = StringEscapeUtils.escapeShell("<less<signs"); + assertEquals("\\<less\\<signs", escaped_str); + } + + /** Simple test that {@link StringEscapeUtils#escapeShell(String)} escapes the or sign. */ + @Test + public void testEscapesOrSigns() { + String escaped_str = StringEscapeUtils.escapeShell("|or|signs"); + assertEquals("\\|or\\|signs", escaped_str); + } } diff --git a/tests/src/com/android/tradefed/util/ZipUtilTest.java b/tests/src/com/android/tradefed/util/ZipUtilTest.java index 33841a092..cabf7fa0f 100644 --- a/tests/src/com/android/tradefed/util/ZipUtilTest.java +++ b/tests/src/com/android/tradefed/util/ZipUtilTest.java @@ -216,8 +216,8 @@ public class ZipUtilTest extends TestCase { partialZipFile, endCentralDirInfo, endCentralDirInfo.getCentralDirOffset()); - // The zip file has 3 folders, 4 files. - assertEquals(7, zipEntries.size()); + // The zip file has 4 folders, 4 files. + assertEquals(8, zipEntries.size()); CentralDirectoryInfo zipEntry; LocalFileHeader localFileHeader; @@ -228,7 +228,7 @@ public class ZipUtilTest extends TestCase { zipEntry = zipEntries .stream() - .filter(e -> e.getFileName().equals("empty_file")) + .filter(e -> e.getFileName().equals("empty/empty_file")) .findFirst() .get(); targetFile = new File(Paths.get(tmpDir.toString(), zipEntry.getFileName()).toString()); @@ -243,6 +243,7 @@ public class ZipUtilTest extends TestCase { // Verify file permissions - readonly - 644 rw-r--r-- permissions = Files.getPosixFilePermissions(targetFile.toPath()); assertEquals(PosixFilePermissions.fromString("rw-r--r--"), permissions); + assertTrue(targetFile.isFile()); // Unzip text file zipEntry = @@ -377,8 +378,8 @@ public class ZipUtilTest extends TestCase { endCentralDirInfo, endCentralDirInfo.getCentralDirOffset(), true); - // The zip file has 3 folders, 4 files. - assertEquals(7, zipEntries.size()); + // The zip file has 4 folders, 4 files. + assertEquals(8, zipEntries.size()); CentralDirectoryInfo zipEntry; LocalFileHeader localFileHeader; @@ -389,7 +390,7 @@ public class ZipUtilTest extends TestCase { zipEntry = zipEntries .stream() - .filter(e -> e.getFileName().equals("empty_file")) + .filter(e -> e.getFileName().equals("empty/empty_file")) .findFirst() .get(); targetFile = new File(Paths.get(tmpDir.toString(), zipEntry.getFileName()).toString()); diff --git a/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java b/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java index db019b604..b3ba0792f 100644 --- a/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java +++ b/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java @@ -31,6 +31,7 @@ import org.mockito.Mockito; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; /** Unit tests for {@link ParallelDeviceExecutor}. */ @@ -93,4 +94,23 @@ public class ParallelDeviceExecutorTest { assertTrue(mExecutor.getErrors().get(0).getMessage().contains("one")); assertTrue(mExecutor.getErrors().get(1).getMessage().contains("two")); } + + @Test + public void testExecution_timeout() { + List<Callable<Boolean>> callableTasks = new ArrayList<>(); + for (ITestDevice device : mDevices) { + callableTasks.add( + () -> { + Thread.sleep(1000L); + return true; + }); + } + + List<Boolean> results = mExecutor.invokeAll(callableTasks, 1L, TimeUnit.MILLISECONDS); + assertEquals(0, results.size()); + assertTrue(mExecutor.hasErrors()); + assertEquals(2, mExecutor.getErrors().size()); + assertTrue(mExecutor.getErrors().get(0) instanceof CancellationException); + assertTrue(mExecutor.getErrors().get(1) instanceof CancellationException); + } } diff --git a/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java b/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java index dd4344050..f45238a55 100644 --- a/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java +++ b/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java @@ -176,12 +176,14 @@ public class MetricUtilTest { any(CollectingByteOutputReceiver.class)); List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID); // Resulting list should have two metrics. - assertThat(data.size()).comparesEqualTo(2); + assertThat(data.size()).isEquivalentAccordingToCompareTo(2); // The first metric should correspond to METRIC_2_* as its timestamp is earlier. - assertThat(data.get(0).getElapsedTimestampNanos()).comparesEqualTo(METRIC_2_NANOS); + assertThat(data.get(0).getElapsedTimestampNanos()) + .isEquivalentAccordingToCompareTo(METRIC_2_NANOS); assertThat(data.get(0).getAtom().hasBleScanResultReceived()).isTrue(); // The second metric should correspond to METRIC_1_*. - assertThat(data.get(1).getElapsedTimestampNanos()).comparesEqualTo(METRIC_1_NANOS); + assertThat(data.get(1).getElapsedTimestampNanos()) + .isEquivalentAccordingToCompareTo(METRIC_1_NANOS); assertThat(data.get(1).getAtom().hasBleScanStateChanged()).isTrue(); } @@ -204,7 +206,7 @@ public class MetricUtilTest { any(CollectingByteOutputReceiver.class)); List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID); // Resulting list should be empty. - assertThat(data.size()).comparesEqualTo(0); + assertThat(data.size()).isEquivalentAccordingToCompareTo(0); } /** @@ -230,12 +232,14 @@ public class MetricUtilTest { any(CollectingByteOutputReceiver.class)); List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID); // Resulting list should have two metrics. - assertThat(data.size()).comparesEqualTo(2); + assertThat(data.size()).isEquivalentAccordingToCompareTo(2); // The first metric should correspond to METRIC_2_* as its timestamp is earlier. - assertThat(data.get(0).getElapsedTimestampNanos()).comparesEqualTo(METRIC_2_NANOS); + assertThat(data.get(0).getElapsedTimestampNanos()) + .isEquivalentAccordingToCompareTo(METRIC_2_NANOS); assertThat(data.get(0).getAtom().hasBleScanResultReceived()).isTrue(); // The second metric should correspond to METRIC_1_*. - assertThat(data.get(1).getElapsedTimestampNanos()).comparesEqualTo(METRIC_1_NANOS); + assertThat(data.get(1).getElapsedTimestampNanos()) + .isEquivalentAccordingToCompareTo(METRIC_1_NANOS); assertThat(data.get(1).getAtom().hasBleScanStateChanged()).isTrue(); } @@ -261,12 +265,14 @@ public class MetricUtilTest { any(CollectingByteOutputReceiver.class)); List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID); // Resulting list should have two metrics. - assertThat(data.size()).comparesEqualTo(2); + assertThat(data.size()).isEquivalentAccordingToCompareTo(2); // The first metric should correspond to METRIC_1_* as its timestamp is earlier. - assertThat(data.get(0).getElapsedTimestampNanos()).comparesEqualTo(METRIC_1_NANOS); + assertThat(data.get(0).getElapsedTimestampNanos()) + .isEquivalentAccordingToCompareTo(METRIC_1_NANOS); assertThat(data.get(0).getAtom().hasBleScanStateChanged()).isTrue(); // The second metric should correspond to METRIC_3_*. - assertThat(data.get(1).getElapsedTimestampNanos()).comparesEqualTo(METRIC_3_NANOS); + assertThat(data.get(1).getElapsedTimestampNanos()) + .isEquivalentAccordingToCompareTo(METRIC_3_NANOS); assertThat(data.get(1).getAtom().hasBleScanStateChanged()).isTrue(); } diff --git a/util-apps/ContentProvider/main/AndroidManifest.xml b/util-apps/ContentProvider/main/AndroidManifest.xml index ac37e685e..15cb3fa30 100644 --- a/util-apps/ContentProvider/main/AndroidManifest.xml +++ b/util-apps/ContentProvider/main/AndroidManifest.xml @@ -19,6 +19,7 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <application> <provider |