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.proto.TestRecordProto.FailureStatus; 33 import com.android.tradefed.testtype.IDeviceTest; 34 import com.android.tradefed.util.CommandResult; 35 import com.android.tradefed.util.CommandStatus; 36 import com.android.tradefed.util.FileUtil; 37 import com.android.tradefed.util.IRunUtil; 38 import com.android.tradefed.util.RunUtil; 39 40 import java.io.File; 41 import java.io.FileOutputStream; 42 import java.io.IOException; 43 import java.util.ArrayList; 44 import java.util.List; 45 46 /** 47 * Test runner for executable running on the host. The runner implements {@link IDeviceTest} since 48 * the host binary might communicate to a device. If the received device is not a {@link StubDevice} 49 * the serial will be passed to the binary to be used. 50 */ 51 @OptionClass(alias = "executable-host-test") 52 public class ExecutableHostTest extends ExecutableBaseTest { 53 54 private static final String ANDROID_SERIAL = "ANDROID_SERIAL"; 55 private static final String LOG_STDOUT_TAG = "-binary-stdout-"; 56 private static final String LOG_STDERR_TAG = "-binary-stderr-"; 57 58 @Option( 59 name = "relative-path-execution", 60 description = 61 "Some scripts assume a relative location to their tests file, this allows to" 62 + " execute with that relative location." 63 ) 64 private boolean mExecuteRelativeToScript = false; 65 66 @Override findBinary(String binary)67 public String findBinary(String binary) { 68 File bin = new File(binary); 69 // If it's a local path or absolute path 70 if (bin.exists()) { 71 return bin.getAbsolutePath(); 72 } 73 if (getTestInfo().getBuildInfo() instanceof IDeviceBuildInfo) { 74 IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo) getTestInfo().getBuildInfo(); 75 File testsDir = deviceBuild.getTestsDir(); 76 77 List<File> scanDirs = new ArrayList<>(); 78 // If it exists, always look first in the ANDROID_HOST_OUT_TESTCASES 79 File targetTestCases = deviceBuild.getFile(BuildInfoFileKey.HOST_LINKED_DIR); 80 if (targetTestCases != null) { 81 scanDirs.add(targetTestCases); 82 } 83 if (testsDir != null) { 84 scanDirs.add(testsDir); 85 } 86 87 try { 88 // Search the full tests dir if no target dir is available. 89 File src = FileUtil.findFile(binary, getAbi(), scanDirs.toArray(new File[] {})); 90 if (src != null) { 91 return src.getAbsolutePath(); 92 } 93 } catch (IOException e) { 94 CLog.e("Failed to find test files from directory."); 95 } 96 } 97 return null; 98 } 99 100 @Override runBinary( String binaryPath, ITestInvocationListener listener, TestDescription description)101 public void runBinary( 102 String binaryPath, ITestInvocationListener listener, TestDescription description) 103 throws DeviceNotAvailableException, IOException { 104 IRunUtil runUtil = createRunUtil(); 105 // Output everything in stdout 106 runUtil.setRedirectStderrToStdout(true); 107 // If we are running against a real device, set ANDROID_SERIAL to the proper serial. 108 if (!(getTestInfo().getDevice().getIDevice() instanceof StubDevice)) { 109 runUtil.setEnvVariable(ANDROID_SERIAL, getTestInfo().getDevice().getSerialNumber()); 110 } 111 // Ensure its executable 112 FileUtil.chmodRWXRecursively(new File(binaryPath)); 113 114 List<String> command = new ArrayList<>(); 115 String scriptName = new File(binaryPath).getName(); 116 if (mExecuteRelativeToScript) { 117 String parentDir = new File(binaryPath).getParent(); 118 command.add("bash"); 119 command.add("-c"); 120 command.add(String.format("pushd %s; ./%s;", parentDir, scriptName)); 121 } else { 122 command.add(binaryPath); 123 } 124 File stdout = FileUtil.createTempFile(scriptName + LOG_STDOUT_TAG, ".txt"); 125 File stderr = FileUtil.createTempFile(scriptName + LOG_STDERR_TAG, ".txt"); 126 127 try (FileOutputStream stdoutStream = new FileOutputStream(stdout); 128 FileOutputStream stderrStream = new FileOutputStream(stderr); ) { 129 CommandResult res = 130 runUtil.runTimedCmd( 131 getTimeoutPerBinaryMs(), 132 stdoutStream, 133 stderrStream, 134 command.toArray(new String[0])); 135 if (!CommandStatus.SUCCESS.equals(res.getStatus())) { 136 FailureStatus status = FailureStatus.TEST_FAILURE; 137 // Everything should be outputted in stdout with our redirect above. 138 String errorMessage = FileUtil.readStringFromFile(stdout); 139 if (CommandStatus.TIMED_OUT.equals(res.getStatus())) { 140 errorMessage += "\nTimeout."; 141 status = FailureStatus.TIMED_OUT; 142 } 143 if (res.getExitCode() != null) { 144 errorMessage += String.format("\nExit Code: %s", res.getExitCode()); 145 } 146 listener.testFailed( 147 description, 148 FailureDescription.create(errorMessage).setFailureStatus(status)); 149 } 150 } finally { 151 logFile(stdout, listener); 152 logFile(stderr, listener); 153 } 154 155 if (!(getTestInfo().getDevice().getIDevice() instanceof StubDevice)) { 156 // Ensure that the binary did not leave the device offline. 157 CLog.d("Checking whether device is still online after %s", binaryPath); 158 try { 159 getTestInfo().getDevice().waitForDeviceAvailable(); 160 } catch (DeviceNotAvailableException e) { 161 listener.testRunFailed( 162 String.format("Device became unavailable after %s.", binaryPath)); 163 throw e; 164 } 165 } 166 } 167 168 @VisibleForTesting createRunUtil()169 IRunUtil createRunUtil() { 170 return new RunUtil(); 171 } 172 logFile(File logFile, ITestLogger logger)173 private void logFile(File logFile, ITestLogger logger) { 174 try (FileInputStreamSource source = new FileInputStreamSource(logFile, true)) { 175 logger.testLog(logFile.getName(), LogDataType.TEXT, source); 176 } 177 } 178 } 179