1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.testtype.binary; 17 18 import com.android.annotations.VisibleForTesting; 19 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey; 20 import com.android.tradefed.build.IDeviceBuildInfo; 21 import com.android.tradefed.config.Option; 22 import com.android.tradefed.config.OptionClass; 23 import com.android.tradefed.device.DeviceNotAvailableException; 24 import com.android.tradefed.device.StubDevice; 25 import com.android.tradefed.log.ITestLogger; 26 import com.android.tradefed.log.LogUtil.CLog; 27 import com.android.tradefed.result.FailureDescription; 28 import com.android.tradefed.result.FileInputStreamSource; 29 import com.android.tradefed.result.ITestInvocationListener; 30 import com.android.tradefed.result.LogDataType; 31 import com.android.tradefed.result.TestDescription; 32 import com.android.tradefed.result.error.DeviceErrorIdentifier; 33 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; 34 import com.android.tradefed.testtype.IDeviceTest; 35 import com.android.tradefed.util.CommandResult; 36 import com.android.tradefed.util.CommandStatus; 37 import com.android.tradefed.util.FileUtil; 38 import com.android.tradefed.util.IRunUtil; 39 import com.android.tradefed.util.RunUtil; 40 41 import java.io.File; 42 import java.io.FileOutputStream; 43 import java.io.IOException; 44 import java.util.ArrayList; 45 import java.util.List; 46 47 /** 48 * Test runner for executable running on the host. The runner implements {@link IDeviceTest} since 49 * the host binary might communicate to a device. If the received device is not a {@link StubDevice} 50 * the serial will be passed to the binary to be used. 51 */ 52 @OptionClass(alias = "executable-host-test") 53 public class ExecutableHostTest extends ExecutableBaseTest { 54 55 private static final String ANDROID_SERIAL = "ANDROID_SERIAL"; 56 private static final String LOG_STDOUT_TAG = "-binary-stdout-"; 57 private static final String LOG_STDERR_TAG = "-binary-stderr-"; 58 59 @Option( 60 name = "relative-path-execution", 61 description = 62 "Some scripts assume a relative location to their tests file, this allows to" 63 + " execute with that relative location." 64 ) 65 private boolean mExecuteRelativeToScript = false; 66 67 @Override findBinary(String binary)68 public String findBinary(String binary) { 69 File bin = new File(binary); 70 // If it's a local path or absolute path 71 if (bin.exists()) { 72 return bin.getAbsolutePath(); 73 } 74 if (getTestInfo().getBuildInfo() instanceof IDeviceBuildInfo) { 75 IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo) getTestInfo().getBuildInfo(); 76 File testsDir = deviceBuild.getTestsDir(); 77 78 List<File> scanDirs = new ArrayList<>(); 79 // If it exists, always look first in the ANDROID_HOST_OUT_TESTCASES 80 File targetTestCases = deviceBuild.getFile(BuildInfoFileKey.HOST_LINKED_DIR); 81 if (targetTestCases != null) { 82 scanDirs.add(targetTestCases); 83 } 84 if (testsDir != null) { 85 scanDirs.add(testsDir); 86 } 87 88 try { 89 // Search the full tests dir if no target dir is available. 90 File src = FileUtil.findFile(binary, getAbi(), scanDirs.toArray(new File[] {})); 91 if (src != null) { 92 return src.getAbsolutePath(); 93 } 94 } catch (IOException e) { 95 CLog.e("Failed to find test files from directory."); 96 } 97 } 98 return null; 99 } 100 101 @Override runBinary( String binaryPath, ITestInvocationListener listener, TestDescription description)102 public void runBinary( 103 String binaryPath, ITestInvocationListener listener, TestDescription description) 104 throws DeviceNotAvailableException, IOException { 105 IRunUtil runUtil = createRunUtil(); 106 // Output everything in stdout 107 runUtil.setRedirectStderrToStdout(true); 108 // If we are running against a real device, set ANDROID_SERIAL to the proper serial. 109 if (!(getTestInfo().getDevice().getIDevice() instanceof StubDevice)) { 110 runUtil.setEnvVariable(ANDROID_SERIAL, getTestInfo().getDevice().getSerialNumber()); 111 } 112 // Ensure its executable 113 FileUtil.chmodRWXRecursively(new File(binaryPath)); 114 115 List<String> command = new ArrayList<>(); 116 String scriptName = new File(binaryPath).getName(); 117 if (mExecuteRelativeToScript) { 118 String parentDir = new File(binaryPath).getParent(); 119 command.add("bash"); 120 command.add("-c"); 121 command.add(String.format("pushd %s; ./%s;", parentDir, scriptName)); 122 } else { 123 command.add(binaryPath); 124 } 125 File stdout = FileUtil.createTempFile(scriptName + LOG_STDOUT_TAG, ".txt"); 126 File stderr = FileUtil.createTempFile(scriptName + LOG_STDERR_TAG, ".txt"); 127 128 try (FileOutputStream stdoutStream = new FileOutputStream(stdout); 129 FileOutputStream stderrStream = new FileOutputStream(stderr); ) { 130 CommandResult res = 131 runUtil.runTimedCmd( 132 getTimeoutPerBinaryMs(), 133 stdoutStream, 134 stderrStream, 135 command.toArray(new String[0])); 136 if (!CommandStatus.SUCCESS.equals(res.getStatus())) { 137 FailureStatus status = FailureStatus.TEST_FAILURE; 138 // Everything should be outputted in stdout with our redirect above. 139 String errorMessage = FileUtil.readStringFromFile(stdout); 140 if (CommandStatus.TIMED_OUT.equals(res.getStatus())) { 141 errorMessage += "\nTimeout."; 142 status = FailureStatus.TIMED_OUT; 143 } 144 if (res.getExitCode() != null) { 145 errorMessage += String.format("\nExit Code: %s", res.getExitCode()); 146 } 147 listener.testFailed( 148 description, 149 FailureDescription.create(errorMessage).setFailureStatus(status)); 150 } 151 } finally { 152 logFile(stdout, listener); 153 logFile(stderr, listener); 154 } 155 156 if (!(getTestInfo().getDevice().getIDevice() instanceof StubDevice)) { 157 // Ensure that the binary did not leave the device offline. 158 CLog.d("Checking whether device is still online after %s", binaryPath); 159 try { 160 getTestInfo().getDevice().waitForDeviceAvailable(); 161 } catch (DeviceNotAvailableException e) { 162 FailureDescription failure = 163 FailureDescription.create( 164 String.format( 165 "Device became unavailable after %s.", binaryPath), 166 FailureStatus.LOST_SYSTEM_UNDER_TEST) 167 .setErrorIdentifier(DeviceErrorIdentifier.DEVICE_UNAVAILABLE) 168 .setCause(e); 169 listener.testRunFailed(failure); 170 throw e; 171 } 172 } 173 } 174 175 @VisibleForTesting createRunUtil()176 IRunUtil createRunUtil() { 177 return new RunUtil(); 178 } 179 logFile(File logFile, ITestLogger logger)180 private void logFile(File logFile, ITestLogger logger) { 181 try (FileInputStreamSource source = new FileInputStreamSource(logFile, true)) { 182 logger.testLog(logFile.getName(), LogDataType.TEXT, source); 183 } 184 } 185 } 186