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