diff options
author | kgui <kgui@google.com> | 2024-01-04 03:09:48 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2024-01-04 03:09:48 +0000 |
commit | 507b4520dcf97ebcfd8caf73ebacdae27e97e60f (patch) | |
tree | ad6976c0ef7af61a039a6015a3807b78fdb5546d | |
parent | 80801f48686f5781c8f18d93a863e1ade1964b1c (diff) | |
parent | bb7dc840badd31240c62ea71282d125873b85617 (diff) | |
download | suite_harness-507b4520dcf97ebcfd8caf73ebacdae27e97e60f.tar.gz |
Add InteractiveResultCollector to pull xTS-Interactive result files from the device to the host. am: a433ddbc3e am: bb7dc840ba
Original change: https://android-review.googlesource.com/c/platform/test/suite_harness/+/2891634
Change-Id: Ia324ab20d8e35275013768c2fd3288a23d46bef4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
2 files changed, 268 insertions, 0 deletions
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/InteractiveResultCollector.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/InteractiveResultCollector.java new file mode 100644 index 00000000..67de0e44 --- /dev/null +++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/InteractiveResultCollector.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.compatibility.common.tradefed.targetprep; + +import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; +import com.android.tradefed.build.IDeviceBuildInfo; +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.invoker.TestInformation; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.result.error.InfraErrorIdentifier; +import com.android.tradefed.targetprep.BaseTargetPreparer; +import com.android.tradefed.targetprep.TargetSetupError; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link ITargetPreparer} that attempts to pull any number of files from device path(s) to any + * xTS report. + * + * <p>Should be performed *before* a new build is flashed, and *after* DeviceSetup is run (if + * enabled). + */ +@OptionClass(alias = "interactive-result-collector") +public class InteractiveResultCollector extends BaseTargetPreparer { + + @Option( + name = "host-path", + description = + "The host-side relative path to the directory where results should be" + + " collected. If not specified, defaults to the 'screenshots' directory.") + private String hostPath = "screenshots"; + + @Option( + name = "device-cleanup", + description = + "Whether all files in the device folers should be cleaned up before test. " + + "Note that the preparer does not verify that files/directories have" + + "been deleted successfully.") + private boolean mCleanup = true; + + @Option( + name = "device-paths", + description = "The list of paths to the files stored on the device.") + private List<String> devicePaths = new ArrayList<>(); + + @Override + public void setUp(TestInformation testInfo) + throws TargetSetupError, DeviceNotAvailableException { + ITestDevice mDevice = testInfo.getDevice(); + if (!(testInfo.getBuildInfo() instanceof IDeviceBuildInfo)) { + throw new TargetSetupError( + "Invalid buildInfo, expecting an IDeviceBuildInfo", + mDevice.getDeviceDescriptor(), + InfraErrorIdentifier.UNDETERMINED); + } + if (mCleanup && !devicePaths.isEmpty()) { + for (String devicePath : devicePaths) { + if (!devicePath.isEmpty()) { + CLog.d("Start clean up path: %s", devicePath); + mDevice.executeAdbCommand("shell", "rm", "-rf", devicePath); + } + } + } + } + + @Override + public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException { + if (e != null && (e instanceof DeviceNotAvailableException)) { + CLog.e("Invocation finished with DeviceNotAvailable, skip collecting results."); + return; + } + + File hostResultDir = null; + if (!devicePaths.isEmpty()) { + try { + hostResultDir = + new File( + new CompatibilityBuildHelper(testInfo.getBuildInfo()) + .getResultDir(), + hostPath); + if (!hostResultDir.exists()) { + hostResultDir.mkdir(); + } + } catch (FileNotFoundException exception) { + CLog.e(exception); + } + } + if (hostResultDir == null) { + // No host result directory, either no device paths, or fail to create it. + return; + } + + ITestDevice testDevice = testInfo.getDevice(); + for (String devicePath : devicePaths) { + if (!devicePath.isEmpty()) { + if (testDevice.pullDir(devicePath, hostResultDir)) { + CLog.d( + String.format( + "Successfully pulled %s to %s.", + devicePath, hostResultDir.getAbsolutePath())); + } + } + } + } +} diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/InteractiveResultCollectorTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/InteractiveResultCollectorTest.java new file mode 100644 index 00000000..e66bf65e --- /dev/null +++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/targetprep/InteractiveResultCollectorTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.compatibility.common.tradefed.targetprep; + +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; +import com.android.tradefed.build.BuildInfo; +import com.android.tradefed.build.DeviceBuildInfo; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.build.IDeviceBuildInfo; +import com.android.tradefed.config.OptionSetter; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.device.NativeDevice; +import com.android.tradefed.invoker.IInvocationContext; +import com.android.tradefed.invoker.InvocationContext; +import com.android.tradefed.invoker.TestInformation; +import com.android.tradefed.targetprep.TargetSetupError; +import com.android.tradefed.util.FileUtil; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; + +/** Unit tests for {@link InteractiveResultCollector}. */ +@RunWith(JUnit4.class) +public final class InteractiveResultCollectorTest { + + private static final String DEVICE_PATH = "/sdcard/documents/xts/screenshot"; + + private final InteractiveResultCollector mCollector = new InteractiveResultCollector(); + private OptionSetter mOptionSetter; + private TestInformation mTestInfo; + + @Test + public void setUp_nonDeviceBuildInfo_throwException() throws Exception { + initTestInfo(new BuildInfo(), mock(NativeDevice.class)); + + assertThrows(TargetSetupError.class, () -> mCollector.setUp(mTestInfo)); + } + + @Test + public void setUp_deviceCleanup_emptyDevicePaths_doNothing() throws Exception { + ITestDevice testDevice = mock(ITestDevice.class); + initTestInfo(new DeviceBuildInfo("0", ""), testDevice); + + mCollector.setUp(mTestInfo); + + verify(testDevice, never()) + .executeAdbCommand(anyString(), anyString(), anyString(), anyString()); + } + + @Test + public void setUp_deviceClenup_emptyDevicePathSkipped() throws Exception { + ITestDevice testDevice = mock(ITestDevice.class); + initTestInfo(new DeviceBuildInfo("0", ""), testDevice); + mOptionSetter.setOptionValue("device-paths", ""); + mOptionSetter.setOptionValue("device-paths", DEVICE_PATH); + mOptionSetter.setOptionValue("device-paths", ""); + + mCollector.setUp(mTestInfo); + + // Only one execution for DEVICE_PATH. + verify(testDevice).executeAdbCommand(anyString(), anyString(), anyString(), anyString()); + verify(testDevice).executeAdbCommand("shell", "rm", "-rf", DEVICE_PATH); + } + + @Test + public void tearDown_deviceNotAvailableException_doNothing() throws Exception { + mCollector.tearDown(/* testInfo= */ null, new DeviceNotAvailableException("", "")); + } + + @Test + public void tearDown_emptyDevicePaths_doNothing() throws Exception { + mCollector.tearDown(/* testInfo= */ null, /* e= */ null); + } + + @Test + public void tearDown_failToGetResultDir_doNothing() throws Exception { + initTestInfo(new DeviceBuildInfo("0", ""), mock(NativeDevice.class)); + mOptionSetter.setOptionValue("device-paths", DEVICE_PATH); + + mCollector.tearDown(mTestInfo, /* e= */ null); + } + + @Test + public void tearDown_pullDir_emptyDevicePathSkipped() throws Exception { + ITestDevice testDevice = mock(ITestDevice.class); + IDeviceBuildInfo buildInfo = new DeviceBuildInfo("0", ""); + // Init the resultDir. + File rootDirForTesting = FileUtil.createTempDir("InteractiveResultCollectorTest"); + buildInfo.addBuildAttribute( + CompatibilityBuildHelper.ROOT_DIR, rootDirForTesting.getAbsolutePath()); + buildInfo.addBuildAttribute(CompatibilityBuildHelper.SUITE_NAME, "cts"); + buildInfo.addBuildAttribute(CompatibilityBuildHelper.START_TIME_MS, "1000000"); + new File(rootDirForTesting, "android-cts").mkdir(); + + initTestInfo(buildInfo, testDevice); + mOptionSetter.setOptionValue("device-paths", ""); + mOptionSetter.setOptionValue("device-paths", DEVICE_PATH); + mOptionSetter.setOptionValue("device-paths", ""); + + mCollector.tearDown(mTestInfo, /* e= */ null); + + // Only one execution for DEVICE_PATH. + verify(testDevice).pullDir(anyString(), any(File.class)); + verify(testDevice).pullDir(eq(DEVICE_PATH), any(File.class)); + } + + /** + * Initializes the {@link TestInformation} for tests by the given {@link IBuildInfo} and {@link + * ITestDevice}. + */ + private void initTestInfo(IBuildInfo buildInfo, ITestDevice testDevice) throws Exception { + IInvocationContext context = new InvocationContext(); + context.addDeviceBuildInfo("device", buildInfo); + context.addAllocatedDevice("device", testDevice); + + mOptionSetter = new OptionSetter(mCollector); + mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build(); + } +} |