diff --git a/src/devtools/mobileharness/infra/ats/server/proto/service.proto b/src/devtools/mobileharness/infra/ats/server/proto/service.proto index 49348243c..78159aed7 100644 --- a/src/devtools/mobileharness/infra/ats/server/proto/service.proto +++ b/src/devtools/mobileharness/infra/ats/server/proto/service.proto @@ -188,6 +188,7 @@ enum ErrorReason { INVALID_RESOURCE = 4; TOO_MANY_LOST_DEVICES = 5; RESULT_PROCESSING_ERROR = 6; + TRADEFED_INVOCATION_ERROR = 7; } // TODO: No longer useful. Remove this proto. diff --git a/src/java/com/google/devtools/mobileharness/infra/ats/common/BUILD b/src/java/com/google/devtools/mobileharness/infra/ats/common/BUILD index 2271be65d..599bd3a96 100644 --- a/src/java/com/google/devtools/mobileharness/infra/ats/common/BUILD +++ b/src/java/com/google/devtools/mobileharness/infra/ats/common/BUILD @@ -228,6 +228,7 @@ java_library( "//src/java/com/google/devtools/mobileharness/platform/android/xts/plugin:__pkg__", "//src/javatests/com/google/devtools/mobileharness/infra/ats/common/jobcreator:__pkg__", "//src/javatests/com/google/devtools/mobileharness/infra/ats/console/controller/sessionplugin:__pkg__", + "//src/javatests/com/google/devtools/mobileharness/infra/ats/server/sessionplugin:__pkg__", "//src/javatests/com/google/devtools/mobileharness/platform/android/xts/plugin:__pkg__", ], deps = ["//src/java/com/google/wireless/qa/mobileharness/shared/constant:property"], diff --git a/src/java/com/google/devtools/mobileharness/infra/ats/common/SessionResultHandlerUtil.java b/src/java/com/google/devtools/mobileharness/infra/ats/common/SessionResultHandlerUtil.java index a5b79d3fe..45ea968e5 100644 --- a/src/java/com/google/devtools/mobileharness/infra/ats/common/SessionResultHandlerUtil.java +++ b/src/java/com/google/devtools/mobileharness/infra/ats/common/SessionResultHandlerUtil.java @@ -835,6 +835,12 @@ private Optional getLabGenFileDir(TestInfo test) { } } + public Path getTradefedInvocationLogDir(TestInfo tradefedTestInfo, Path logRootDir) + throws MobileHarnessException { + Path invocationDir = prepareTradefedInvocationDir(tradefedTestInfo, logRootDir); + return prepareLogOrResultDirForTest(tradefedTestInfo, invocationDir); + } + private Path prepareTradefedInvocationDir(TestInfo tradefedTestInfo, Path logRootDir) { Path invocationDir; if (tradefedTestInfo.properties().has(XtsConstants.TRADEFED_INVOCATION_DIR_NAME)) { diff --git a/src/java/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/BUILD b/src/java/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/BUILD index 30f676637..9ca5fb709 100644 --- a/src/java/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/BUILD +++ b/src/java/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/BUILD @@ -88,6 +88,7 @@ java_library( "//src/java/com/google/devtools/mobileharness/infra/client/longrunningservice/model:session_info", "//src/java/com/google/devtools/mobileharness/infra/lab/common/dir", "//src/java/com/google/devtools/mobileharness/platform/android/xts/common/util:xts_constants", + "//src/java/com/google/devtools/mobileharness/platform/android/xts/runtime:xts_tradefed_runtime_info_file_util", "//src/java/com/google/devtools/mobileharness/shared/util/auto:auto_value", "//src/java/com/google/devtools/mobileharness/shared/util/command", "//src/java/com/google/devtools/mobileharness/shared/util/error:more_throwables", diff --git a/src/java/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/NewMultiCommandRequestHandler.java b/src/java/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/NewMultiCommandRequestHandler.java index ec2c4c5f8..0adc00567 100644 --- a/src/java/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/NewMultiCommandRequestHandler.java +++ b/src/java/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/NewMultiCommandRequestHandler.java @@ -16,9 +16,11 @@ package com.google.devtools.mobileharness.infra.ats.server.sessionplugin; +import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Throwables.getStackTraceAsString; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.Iterables.getLast; import static com.google.devtools.mobileharness.shared.util.error.MoreThrowables.shortDebugString; import static com.google.devtools.mobileharness.shared.util.time.TimeUtils.toJavaDuration; import static java.nio.charset.StandardCharsets.UTF_8; @@ -44,6 +46,7 @@ import com.google.devtools.mobileharness.infra.ats.common.SessionRequestInfo; import com.google.devtools.mobileharness.infra.ats.common.SessionResultHandlerUtil; import com.google.devtools.mobileharness.infra.ats.common.XtsPropertyName; +import com.google.devtools.mobileharness.infra.ats.common.XtsPropertyName.Job; import com.google.devtools.mobileharness.infra.ats.common.XtsTypeLoader; import com.google.devtools.mobileharness.infra.ats.common.jobcreator.XtsJobCreator; import com.google.devtools.mobileharness.infra.ats.common.proto.XtsCommonProto.ShardingMode; @@ -62,6 +65,8 @@ import com.google.devtools.mobileharness.infra.client.longrunningservice.model.SessionInfo; import com.google.devtools.mobileharness.infra.lab.common.dir.DirUtil; import com.google.devtools.mobileharness.platform.android.xts.common.util.XtsConstants; +import com.google.devtools.mobileharness.platform.android.xts.runtime.XtsTradefedRuntimeInfoFileUtil; +import com.google.devtools.mobileharness.platform.android.xts.runtime.XtsTradefedRuntimeInfoFileUtil.XtsTradefedRuntimeInfoFileDetail; import com.google.devtools.mobileharness.shared.util.command.Command; import com.google.devtools.mobileharness.shared.util.command.CommandExecutor; import com.google.devtools.mobileharness.shared.util.file.local.LocalFileUtil; @@ -70,6 +75,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.protobuf.util.Timestamps; import com.google.wireless.qa.mobileharness.shared.model.job.JobInfo; +import com.google.wireless.qa.mobileharness.shared.model.job.TestInfo; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -118,6 +124,7 @@ final class NewMultiCommandRequestHandler { private final SessionRequestHandlerUtil sessionRequestHandlerUtil; private final SessionResultHandlerUtil sessionResultHandlerUtil; private final LocalFileUtil localFileUtil; + private final XtsTradefedRuntimeInfoFileUtil xtsTradefedRuntimeInfoFileUtil; private final CommandExecutor commandExecutor; private final Clock clock; private final XtsTypeLoader xtsTypeLoader; @@ -136,6 +143,7 @@ final class NewMultiCommandRequestHandler { SessionRequestHandlerUtil sessionRequestHandlerUtil, SessionResultHandlerUtil sessionResultHandlerUtil, LocalFileUtil localFileUtil, + XtsTradefedRuntimeInfoFileUtil xtsTradefedRuntimeInfoFileUtil, CommandExecutor commandExecutor, Clock clock, XtsTypeLoader xtsTypeLoader, @@ -143,6 +151,7 @@ final class NewMultiCommandRequestHandler { this.sessionRequestHandlerUtil = sessionRequestHandlerUtil; this.sessionResultHandlerUtil = sessionResultHandlerUtil; this.localFileUtil = localFileUtil; + this.xtsTradefedRuntimeInfoFileUtil = xtsTradefedRuntimeInfoFileUtil; this.commandExecutor = commandExecutor; this.clock = clock; this.xtsTypeLoader = xtsTypeLoader; @@ -625,24 +634,26 @@ private HandleResultProcessingResult handleResultProcessingInternal( for (CommandDetail commandDetail : commandDetails) { CommandDetail.Builder commandDetailBuilder = commandDetail.toBuilder(); String commandId = commandDetail.getId(); + Path outputDirPath = + Path.of(outputUrl.getPath()).resolve(sessionInfo.getSessionId()).resolve(commandId); + String resultDirectoryName = + TIMESTAMP_DIR_NAME_FORMATTER.format(Instant.now()) + "_" + getRandom4Digits(); + Path resultDir = outputDirPath.resolve(resultDirectoryName); + Path logDir = outputDirPath.resolve("logs"); + ImmutableList jobs = + commandToJobsMap.get(commandId).stream() + .map(jobIdToJobMap::get) + .collect(toImmutableList()); try { SessionRequestInfo sessionRequestInfo = getSessionRequestInfo(request, commandDetail.getOriginalCommandInfo(), sessionInfo); - Path outputDirPath = - Path.of(outputUrl.getPath()).resolve(sessionInfo.getSessionId()).resolve(commandId); - String resultDirectoryName = - TIMESTAMP_DIR_NAME_FORMATTER.format(Instant.now()) + "_" + getRandom4Digits(); - Path resultDir = outputDirPath.resolve(resultDirectoryName); - Path logDir = outputDirPath.resolve("logs"); Optional processResult = sessionResultHandlerUtil.processResult( resultDir, logDir, /* latestResultLink= */ null, /* latestLogLink= */ null, - commandToJobsMap.get(commandId).stream() - .map(jobIdToJobMap::get) - .collect(toImmutableList()), + jobs, sessionRequestInfo); if (processResult.isPresent() && processResult.get().hasSummary()) { long failedModuleCount = @@ -711,7 +722,7 @@ private HandleResultProcessingResult handleResultProcessingInternal( ErrorReason.RESULT_PROCESSING_ERROR, "No valid test cases found in the result."); } - // TODO: Collect state and error message from TF agent if exists. + checkTradefedInvocationError(commandDetailBuilder, jobs, logDir); } commandDetailBuilder .setEndTime(Timestamps.fromMillis(clock.millis())) @@ -770,6 +781,67 @@ void cleanup(SessionInfo sessionInfo) throws InterruptedException { } } + /** + * Reads the Tradefed runtime info file and checks for the Tradefed invocation error message, and + * set it in the command detail proto if present. + */ + private void checkTradefedInvocationError( + CommandDetail.Builder commandDetailBuilder, ImmutableList jobs, Path logDir) { + ImmutableList tradefedTestInfos = + jobs.stream() + .filter(jobInfo -> jobInfo.properties().getBoolean(Job.IS_XTS_TF_JOB).orElse(false)) + .flatMap(jobInfo -> jobInfo.tests().getAll().values().stream()) + .collect(toImmutableList()); + + tradefedTestInfos.forEach( + tradefedTestInfo -> { + Path testLogDir; + try { + testLogDir = + sessionResultHandlerUtil.getTradefedInvocationLogDir(tradefedTestInfo, logDir); + } catch (MobileHarnessException e) { + logger.atWarning().withCause(e).log( + "Failed to get Tradefed invocation log dir for test %s", + tradefedTestInfo.locator().getId()); + return; + } + + Path runtimeInfoFilePath = + testLogDir.resolve(XtsConstants.TRADEFED_RUNTIME_INFO_FILE_NAME); + if (!localFileUtil.isFileExist(runtimeInfoFilePath)) { + return; + } + + Optional fileDetailOptional; + try { + fileDetailOptional = + xtsTradefedRuntimeInfoFileUtil.readInfo( + runtimeInfoFilePath, /* lastModifiedTime= */ null); + } catch (IOException | RuntimeException | Error e) { + logger.atWarning().withCause(e).log( + "Failed to read Tradefed runtime info of test %s from file %s", + tradefedTestInfo.locator().getId(), runtimeInfoFilePath); + return; + } + + if (fileDetailOptional.isEmpty()) { + return; + } + + String errorMessage = + getLast(fileDetailOptional.get().runtimeInfo().invocations()).errorMessage(); + + if (!isNullOrEmpty(errorMessage)) { + setCommandError( + commandDetailBuilder, + ErrorReason.TRADEFED_INVOCATION_ERROR, + commandDetailBuilder.getErrorMessage().isEmpty() + ? errorMessage + : commandDetailBuilder.getErrorMessage() + "\n" + errorMessage); + } + }); + } + private URL getTestResourceUrl(TestResource testResource) throws MobileHarnessException { try { return URI.create(testResource.getUrl()).toURL(); diff --git a/src/java/com/google/devtools/mobileharness/platform/android/xts/common/util/XtsConstants.java b/src/java/com/google/devtools/mobileharness/platform/android/xts/common/util/XtsConstants.java index 0231177ab..97a575da7 100644 --- a/src/java/com/google/devtools/mobileharness/platform/android/xts/common/util/XtsConstants.java +++ b/src/java/com/google/devtools/mobileharness/platform/android/xts/common/util/XtsConstants.java @@ -43,6 +43,8 @@ public class XtsConstants { */ public static final String TRADEFED_RUNTIME_INFO_FILE_PATH = "tf_runtime_info_file_path"; + public static final String TRADEFED_RUNTIME_INFO_FILE_NAME = "tf_runtime_info"; + public static final String INVOCATION_SUMMARY_FILE_NAME = "invocation_summary.txt"; /** A MH job property key to indicate whether xTS dynamic download is enabled. */ diff --git a/src/java/com/google/devtools/mobileharness/platform/android/xts/runtime/BUILD b/src/java/com/google/devtools/mobileharness/platform/android/xts/runtime/BUILD index b164d454f..e73273a0e 100644 --- a/src/java/com/google/devtools/mobileharness/platform/android/xts/runtime/BUILD +++ b/src/java/com/google/devtools/mobileharness/platform/android/xts/runtime/BUILD @@ -22,6 +22,8 @@ java_library( srcs = ["XtsTradefedRuntimeInfoFileUtil.java"], visibility = [ "//src/java/com/google/devtools/mobileharness/infra/ats/console/controller/sessionplugin:__pkg__", + "//src/java/com/google/devtools/mobileharness/infra/ats/server/sessionplugin:__pkg__", + "//src/javatests/com/google/devtools/mobileharness/infra/ats/server/sessionplugin:__pkg__", "//src/javatests/com/google/devtools/mobileharness/platform/android/xts/agent:__pkg__", ], deps = [ @@ -35,6 +37,7 @@ java_library( srcs = ["XtsTradefedRuntimeInfo.java"], visibility = [ "//src/java/com/google/devtools/mobileharness/infra/ats/console/controller/sessionplugin:__pkg__", + "//src/javatests/com/google/devtools/mobileharness/infra/ats/server/sessionplugin:__pkg__", "//src/javatests/com/google/devtools/mobileharness/platform/android/xts/agent:__pkg__", "//src/javatests/com/google/devtools/mobileharness/platform/android/xts/runtime:__pkg__", ], diff --git a/src/java/com/google/wireless/qa/mobileharness/shared/api/driver/XtsTradefedTest.java b/src/java/com/google/wireless/qa/mobileharness/shared/api/driver/XtsTradefedTest.java index 54cd1ee9a..d7405a66d 100644 --- a/src/java/com/google/wireless/qa/mobileharness/shared/api/driver/XtsTradefedTest.java +++ b/src/java/com/google/wireless/qa/mobileharness/shared/api/driver/XtsTradefedTest.java @@ -357,7 +357,8 @@ private Optional runXtsCommand( getEnvironmentToTradefedConsole(tmpXtsRootDir, xtsType, spec); // Create runtime info file path. - Path runtimeInfoFilePath = Path.of(testInfo.getGenFileDir()).resolve("tf_runtime_info"); + Path runtimeInfoFilePath = + Path.of(testInfo.getGenFileDir()).resolve(XtsConstants.TRADEFED_RUNTIME_INFO_FILE_NAME); testInfo .properties() .add(XtsConstants.TRADEFED_RUNTIME_INFO_FILE_PATH, runtimeInfoFilePath.toString()); diff --git a/src/javatests/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/BUILD b/src/javatests/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/BUILD index 2d1b7da47..39c504b82 100644 --- a/src/javatests/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/BUILD +++ b/src/javatests/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/BUILD @@ -47,6 +47,7 @@ java_library( "//src/java/com/google/devtools/mobileharness/infra/ats/common:session_request_handler_util", "//src/java/com/google/devtools/mobileharness/infra/ats/common:session_request_info", "//src/java/com/google/devtools/mobileharness/infra/ats/common:session_result_handler_util", + "//src/java/com/google/devtools/mobileharness/infra/ats/common:xts_property_name", "//src/java/com/google/devtools/mobileharness/infra/ats/common:xts_type_loader", "//src/java/com/google/devtools/mobileharness/infra/ats/common/jobcreator:xts_job_creator", "//src/java/com/google/devtools/mobileharness/infra/ats/server/sessionplugin:ats_server_session_plugin_lib", @@ -60,6 +61,8 @@ java_library( "//src/java/com/google/devtools/mobileharness/infra/controller/scheduler/model/job/in:device_requirements", "//src/java/com/google/devtools/mobileharness/infra/lab/common/dir", "//src/java/com/google/devtools/mobileharness/platform/android/xts/common/util:xts_constants", + "//src/java/com/google/devtools/mobileharness/platform/android/xts/runtime:xts_tradefed_runtime_info", + "//src/java/com/google/devtools/mobileharness/platform/android/xts/runtime:xts_tradefed_runtime_info_file_util", "//src/java/com/google/devtools/mobileharness/shared/util/command", "//src/java/com/google/devtools/mobileharness/shared/util/file/local", "//src/java/com/google/devtools/mobileharness/shared/util/flags", diff --git a/src/javatests/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/NewMultiCommandRequestHandlerTest.java b/src/javatests/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/NewMultiCommandRequestHandlerTest.java index 1385d3928..63ca7e967 100644 --- a/src/javatests/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/NewMultiCommandRequestHandlerTest.java +++ b/src/javatests/com/google/devtools/mobileharness/infra/ats/server/sessionplugin/NewMultiCommandRequestHandlerTest.java @@ -22,12 +22,14 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; @@ -37,6 +39,7 @@ import com.google.devtools.mobileharness.infra.ats.common.SessionRequestHandlerUtil; import com.google.devtools.mobileharness.infra.ats.common.SessionRequestInfo; import com.google.devtools.mobileharness.infra.ats.common.SessionResultHandlerUtil; +import com.google.devtools.mobileharness.infra.ats.common.XtsPropertyName.Job; import com.google.devtools.mobileharness.infra.ats.common.XtsTypeLoader; import com.google.devtools.mobileharness.infra.ats.common.jobcreator.XtsJobCreator; import com.google.devtools.mobileharness.infra.ats.console.result.proto.ReportProto.Module; @@ -57,6 +60,11 @@ import com.google.devtools.mobileharness.infra.client.longrunningservice.constant.SessionProperties; import com.google.devtools.mobileharness.infra.client.longrunningservice.model.SessionInfo; import com.google.devtools.mobileharness.infra.lab.common.dir.DirUtil; +import com.google.devtools.mobileharness.platform.android.xts.common.util.XtsConstants; +import com.google.devtools.mobileharness.platform.android.xts.runtime.XtsTradefedRuntimeInfo; +import com.google.devtools.mobileharness.platform.android.xts.runtime.XtsTradefedRuntimeInfo.TradefedInvocation; +import com.google.devtools.mobileharness.platform.android.xts.runtime.XtsTradefedRuntimeInfoFileUtil; +import com.google.devtools.mobileharness.platform.android.xts.runtime.XtsTradefedRuntimeInfoFileUtil.XtsTradefedRuntimeInfoFileDetail; import com.google.devtools.mobileharness.shared.util.command.Command; import com.google.devtools.mobileharness.shared.util.command.CommandException; import com.google.devtools.mobileharness.shared.util.command.CommandExecutor; @@ -67,6 +75,8 @@ import com.google.inject.testing.fieldbinder.BoundFieldModule; import com.google.wireless.qa.mobileharness.shared.model.job.JobInfo; import com.google.wireless.qa.mobileharness.shared.model.job.JobLocator; +import com.google.wireless.qa.mobileharness.shared.model.job.TestInfo; +import com.google.wireless.qa.mobileharness.shared.model.job.TestInfos; import com.google.wireless.qa.mobileharness.shared.model.job.in.Files; import com.google.wireless.qa.mobileharness.shared.model.job.out.Properties; import com.google.wireless.qa.mobileharness.shared.model.job.out.Timing; @@ -102,6 +112,9 @@ public final class NewMultiCommandRequestHandlerTest { private static final String DEFAULT_COMMAND_LINE = "cts-plan --module module1 --test test1 --logcat-on-failure --shard-count 2" + " --parallel-setup true --parallel-setup-timeout 0"; + private static final String DEVICE_ID_1 = "device_id_1"; + private static final String DEVICE_ID_2 = "device_id_2"; + private static final Path TRADEFED_INVOCATION_LOG_DIR = Path.of("/tradefed_invocation_log_dir"); private CommandInfo commandInfo = CommandInfo.getDefaultInstance(); private NewMultiCommandRequest request = NewMultiCommandRequest.getDefaultInstance(); @@ -118,10 +131,13 @@ public final class NewMultiCommandRequestHandlerTest { @Bind @Mock private Clock clock; @Bind @Mock private XtsTypeLoader xtsTypeLoader; @Bind @Spy private LocalFileUtil localFileUtil = new LocalFileUtil(); + @Bind @Mock private XtsTradefedRuntimeInfoFileUtil xtsTradefedRuntimeInfoFileUtil; @Mock private SessionInfo sessionInfo; @Mock private JobInfo jobInfo; @Mock private Files files; + @Mock private TestInfo testInfo; + @Mock private TestInfos testInfos; private final Properties properties = new Properties(new Timing()); @Captor private ArgumentCaptor sessionRequestInfoCaptor; @@ -134,18 +150,21 @@ public void setup() throws Exception { Flags.parse(new String[] {String.format("--public_dir=%s", publicDir)}); Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); when(sessionInfo.getSessionId()).thenReturn("session_id"); + properties.add(Job.IS_XTS_TF_JOB, "true"); when(jobInfo.locator()).thenReturn(new JobLocator("job_id", "job_name")); when(jobInfo.properties()).thenReturn(properties); when(jobInfo.files()).thenReturn(files); + when(jobInfo.tests()).thenReturn(testInfos); + when(testInfos.getAll()).thenReturn(ImmutableListMultimap.of("test_id", testInfo)); when(sessionRequestHandlerUtil.addNonTradefedModuleInfo(any())) .thenAnswer(invocation -> invocation.getArgument(0)); when(deviceQuerier.queryDevice(any())) .thenReturn( DeviceQueryResult.newBuilder() .addDeviceInfo( - DeviceInfo.newBuilder().setId("device_id_1").addType("AndroidOnlineDevice")) + DeviceInfo.newBuilder().setId(DEVICE_ID_1).addType("AndroidOnlineDevice")) .addDeviceInfo( - DeviceInfo.newBuilder().setId("device_id_2").addType("AndroidOnlineDevice")) + DeviceInfo.newBuilder().setId(DEVICE_ID_2).addType("AndroidOnlineDevice")) .build()); String xtsRootDir = DirUtil.getPublicGenDir() + "/session_session_id/file"; when(xtsTypeLoader.getXtsType(eq(xtsRootDir), any())).thenReturn("cts"); @@ -156,12 +175,12 @@ public void setup() throws Exception { .addDeviceDimensions( CommandInfo.DeviceDimension.newBuilder() .setName("device_serial") - .setValue("device_id_1") + .setValue(DEVICE_ID_1) .build()) .addDeviceDimensions( CommandInfo.DeviceDimension.newBuilder() .setName("device_serial") - .setValue("device_id_2") + .setValue(DEVICE_ID_2) .build()) .build(); outputFileUploadPath = tmpFolder.newFolder("output_file_upload_path").getAbsolutePath(); @@ -196,6 +215,12 @@ public void setup() throws Exception { when(clock.millis()).thenReturn(1000L); when(sessionInfo.getSessionProperty(SessionProperties.PROPERTY_KEY_SERVER_SESSION_LOG_PATH)) .thenReturn(Optional.of("/path/to/server_session_log.txt")); + when(sessionResultHandlerUtil.getTradefedInvocationLogDir(any(), any())) + .thenReturn(TRADEFED_INVOCATION_LOG_DIR); + doReturn(false) + .when(localFileUtil) + .isFileExist( + eq(TRADEFED_INVOCATION_LOG_DIR.resolve(XtsConstants.TRADEFED_RUNTIME_INFO_FILE_NAME))); } @After @@ -212,12 +237,12 @@ public void createTradefedJobs_invalidCommandLine() throws Exception { .addDeviceDimensions( CommandInfo.DeviceDimension.newBuilder() .setName("device_serial") - .setValue("device_id_1") + .setValue(DEVICE_ID_1) .build()) .addDeviceDimensions( CommandInfo.DeviceDimension.newBuilder() .setName("device_serial") - .setValue("device_id_2") + .setValue(DEVICE_ID_2) .build()) .build(); request = @@ -281,7 +306,7 @@ public void createTradefedJobs_success() throws Exception { assertThat(sessionRequestInfo.androidXtsZip()).hasValue("ats-file-server::" + zipFile); assertThat(sessionRequestInfo.startTimeout()).isEqualTo(Duration.ofSeconds(1000)); assertThat(sessionRequestInfo.jobTimeout()).isEqualTo(Duration.ofSeconds(2000)); - assertThat(sessionRequestInfo.deviceSerials()).containsExactly("device_id_1", "device_id_2"); + assertThat(sessionRequestInfo.deviceSerials()).containsExactly(DEVICE_ID_1, DEVICE_ID_2); assertThat(sessionRequestInfo.shardCount()).hasValue(2); assertThat(sessionRequestInfo.envVars()).containsExactly("env_key1", "env_value1"); assertThat(sessionRequestInfo.testPlanFile()).hasValue("ats-file-server::" + testPlanFile); @@ -300,9 +325,7 @@ public void createTradefedJobs_fromRetrySession_success() throws Exception { when(clock.millis()).thenReturn(1000L).thenReturn(2000L).thenReturn(3000L); when(xtsJobCreator.createXtsTradefedTestJob(any())).thenReturn(ImmutableList.of(jobInfo)); when(commandExecutor.run(any())).thenReturn("COMMAND_OUTPUT"); - Mockito.doReturn(Path.of("/path/to/previous_result.pb")) - .when(localFileUtil) - .checkFile(any(Path.class)); + doReturn(Path.of("/path/to/previous_result.pb")).when(localFileUtil).checkFile(any(Path.class)); String expectedCommandId = UUID.nameUUIDFromBytes(commandInfo.getCommandLine().getBytes(UTF_8)).toString(); String retryCommandLine = "retry --retry 1"; @@ -351,7 +374,7 @@ public void createTradefedJobs_fromRetrySession_success() throws Exception { assertThat(sessionRequestInfo.androidXtsZip()).hasValue("ats-file-server::" + zipFile); assertThat(sessionRequestInfo.startTimeout()).isEqualTo(Duration.ofSeconds(1000)); assertThat(sessionRequestInfo.jobTimeout()).isEqualTo(Duration.ofSeconds(2000)); - assertThat(sessionRequestInfo.deviceSerials()).containsExactly("device_id_1", "device_id_2"); + assertThat(sessionRequestInfo.deviceSerials()).containsExactly(DEVICE_ID_1, DEVICE_ID_2); assertThat(sessionRequestInfo.envVars()).containsExactly("env_key1", "env_value1"); assertThat(sessionRequestInfo.retrySessionId()).hasValue("retry_previous_session_id"); String retryResultDir = @@ -423,7 +446,7 @@ public void createTradefedJobs_retryResultNotFound_runAsNewAttempt() throws Exce assertThat(sessionRequestInfo.androidXtsZip()).hasValue("ats-file-server::" + zipFile); assertThat(sessionRequestInfo.startTimeout()).isEqualTo(Duration.ofSeconds(1000)); assertThat(sessionRequestInfo.jobTimeout()).isEqualTo(Duration.ofSeconds(2000)); - assertThat(sessionRequestInfo.deviceSerials()).containsExactly("device_id_1", "device_id_2"); + assertThat(sessionRequestInfo.deviceSerials()).containsExactly(DEVICE_ID_1, DEVICE_ID_2); assertThat(sessionRequestInfo.shardCount()).hasValue(2); assertThat(sessionRequestInfo.envVars()).containsExactly("env_key1", "env_value1"); assertThat(sessionRequestInfo.retrySessionId()).isEmpty(); @@ -443,9 +466,7 @@ public void createTradefedJobs_fromRetryTestRun_success() throws Exception { when(commandExecutor.run(any())).thenReturn("COMMAND_OUTPUT"); String expectedCommandId = UUID.nameUUIDFromBytes(commandInfo.getCommandLine().getBytes(UTF_8)).toString(); - Mockito.doReturn(Path.of("/path/to/previous_result.pb")) - .when(localFileUtil) - .checkFile(any(Path.class)); + doReturn(Path.of("/path/to/previous_result.pb")).when(localFileUtil).checkFile(any(Path.class)); request = request.toBuilder() .setPrevTestContext( @@ -490,7 +511,7 @@ public void createTradefedJobs_fromRetryTestRun_success() throws Exception { assertThat(sessionRequestInfo.androidXtsZip()).hasValue("ats-file-server::" + zipFile); assertThat(sessionRequestInfo.startTimeout()).isEqualTo(Duration.ofSeconds(1000)); assertThat(sessionRequestInfo.jobTimeout()).isEqualTo(Duration.ofSeconds(2000)); - assertThat(sessionRequestInfo.deviceSerials()).containsExactly("device_id_1", "device_id_2"); + assertThat(sessionRequestInfo.deviceSerials()).containsExactly(DEVICE_ID_1, DEVICE_ID_2); assertThat(sessionRequestInfo.shardCount()).hasValue(2); assertThat(sessionRequestInfo.envVars()).containsExactly("env_key1", "env_value1"); assertThat(sessionRequestInfo.retrySessionId()).hasValue("retry_previous_session_id"); @@ -542,7 +563,7 @@ public void createTradefedJobsWithInvalidResource_addResultToSessionOutput() thr .addDeviceDimensions( CommandInfo.DeviceDimension.newBuilder() .setName("device_serial") - .setValue("device_id_1") + .setValue(DEVICE_ID_1) .build()) .build(); NewMultiCommandRequest request = @@ -652,7 +673,7 @@ public void createNonTradefedJobs_invalidRequest_returnEmptyCommandList() throws .addDeviceDimensions( CommandInfo.DeviceDimension.newBuilder() .setName("device_serial") - .setValue("device_id_1") + .setValue(DEVICE_ID_1) .build()) .build(); NewMultiCommandRequest request = @@ -856,6 +877,50 @@ public void handleResultProcessing_nonFileUrl_onlyCleanup() throws Exception { verify(sessionResultHandlerUtil).cleanUpJobGenDirs(ImmutableList.of(jobInfo)); } + @Test + public void handleResultProcessing_tradefedError_failWithErrorMessage() throws Exception { + Result result = + Result.newBuilder() + .setSummary(Summary.newBuilder().setPassed(9).setFailed(1).build()) + .build(); + mockProcessResult(result); + + doReturn(true) + .when(localFileUtil) + .isFileExist( + eq(TRADEFED_INVOCATION_LOG_DIR.resolve(XtsConstants.TRADEFED_RUNTIME_INFO_FILE_NAME))); + // Mock the content of the tradefed invocation runtime info log file. + String tradefedInvocationErrorMessage = "example error message"; + when(xtsTradefedRuntimeInfoFileUtil.readInfo(any(), any())) + .thenReturn( + Optional.of( + new XtsTradefedRuntimeInfoFileDetail( + new XtsTradefedRuntimeInfo( + ImmutableList.of( + new TradefedInvocation( + ImmutableList.of(DEVICE_ID_1, DEVICE_ID_2), + "failed", + tradefedInvocationErrorMessage)), + /* timestamp= */ Instant.now()), + /* lastModifiedTime= */ Instant.now()))); + + HandleResultProcessingResult handleResultProcessingResult = + createJobAndHandleResultProcessing(request); + + assertThat(handleResultProcessingResult.commandDetails()).hasSize(1); + CommandDetail commandDetail = + handleResultProcessingResult.commandDetails().values().iterator().next(); + assertThat(commandDetail.getPassedTestCount()).isEqualTo(9); + assertThat(commandDetail.getFailedTestCount()).isEqualTo(1); + assertThat(commandDetail.getTotalTestCount()).isEqualTo(10); + String commandId = + UUID.nameUUIDFromBytes(commandInfo.getCommandLine().getBytes(UTF_8)).toString(); + assertThat(commandDetail.getId()).isEqualTo(commandId); + assertThat(commandDetail.getState()).isEqualTo(CommandState.ERROR); + assertThat(commandDetail.getErrorReason()).isEqualTo(ErrorReason.TRADEFED_INVOCATION_ERROR); + assertThat(commandDetail.getErrorMessage()).isEqualTo(tradefedInvocationErrorMessage); + } + @Test public void cleanup_success() throws Exception { when(xtsJobCreator.createXtsTradefedTestJob(any())).thenReturn(ImmutableList.of(jobInfo));