1 /*
2  * Copyright (C) 2018 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 
17 package com.android.tradefed.testtype;
18 
19 import com.android.ddmlib.IShellOutputReceiver;
20 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
21 import com.android.tradefed.build.DeviceBuildInfo;
22 import com.android.tradefed.build.IBuildInfo;
23 import com.android.tradefed.config.OptionClass;
24 import com.android.tradefed.device.DeviceNotAvailableException;
25 import com.android.tradefed.invoker.TestInformation;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.result.ITestInvocationListener;
28 import com.android.tradefed.util.CommandResult;
29 import com.android.tradefed.util.CommandStatus;
30 import com.android.tradefed.util.FileUtil;
31 import com.android.tradefed.util.RunUtil;
32 import com.android.tradefed.util.ShellOutputReceiverStream;
33 
34 import org.json.JSONException;
35 import org.json.JSONObject;
36 
37 import java.io.File;
38 import java.io.IOException;
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 /** A Test that runs a native test package. */
43 @OptionClass(alias = "hostgtest")
44 public class HostGTest extends GTestBase implements IAbiReceiver, IBuildReceiver {
45     private static final long DEFAULT_HOST_COMMAND_TIMEOUT_MS = 2 * 60 * 1000;
46 
47     private IBuildInfo mBuildInfo = null;
48     private IAbi mAbi = null;
49 
50     @Override
setAbi(IAbi abi)51     public void setAbi(IAbi abi) {
52         this.mAbi = abi;
53     }
54 
55     @Override
getAbi()56     public IAbi getAbi() {
57         return this.mAbi;
58     }
59 
60     @Override
setBuild(IBuildInfo buildInfo)61     public void setBuild(IBuildInfo buildInfo) {
62         this.mBuildInfo = buildInfo;
63     }
64 
65     /**
66      * @param cmd command that want to execute in host
67      * @return the {@link CommandResult} of command
68      */
executeHostCommand(String cmd)69     public CommandResult executeHostCommand(String cmd) {
70         return executeHostCommand(cmd, DEFAULT_HOST_COMMAND_TIMEOUT_MS);
71     }
72 
73     /**
74      * @param cmd command that want to execute in host
75      * @param timeoutMs timeout for command in milliseconds
76      * @return the {@link CommandResult} of command
77      */
executeHostCommand(String cmd, long timeoutMs)78     public CommandResult executeHostCommand(String cmd, long timeoutMs) {
79         String[] cmds = cmd.split("\\s+");
80         return RunUtil.getDefault().runTimedCmd(timeoutMs, cmds);
81     }
82 
83     /**
84      * @param cmd command that want to execute in host
85      * @param timeoutMs timeout for command in milliseconds
86      * @param receiver the result parser
87      * @return the {@link CommandResult} of command
88      */
executeHostGTestCommand( String cmd, long timeoutMs, IShellOutputReceiver receiver)89     public CommandResult executeHostGTestCommand(
90             String cmd, long timeoutMs, IShellOutputReceiver receiver) {
91         RunUtil runUtil = new RunUtil();
92         String[] cmds = cmd.split("\\s+");
93 
94         if (getShardCount() > 0) {
95             if (isCollectTestsOnly()) {
96                 CLog.w(
97                         "--collect-tests-only option ignores sharding parameters, and will cause "
98                                 + "each shard to collect all tests.");
99             }
100             runUtil.setEnvVariable("GTEST_SHARD_INDEX", Integer.toString(getShardIndex()));
101             runUtil.setEnvVariable("GTEST_TOTAL_SHARDS", Integer.toString(getShardCount()));
102         }
103 
104         // Set the RunUtil to combine stderr with stdout so that they are interleaved correctly.
105         runUtil.setRedirectStderrToStdout(true);
106 
107         // If there's a shell output receiver to pass results along to, then
108         // ShellOutputReceiverStream will write that into the IShellOutputReceiver. If not, the
109         // command output will just be ignored.
110         CommandResult result;
111         try (ShellOutputReceiverStream stream = new ShellOutputReceiverStream(receiver)) {
112             result = runUtil.runTimedCmd(timeoutMs, stream, null, cmds);
113         } catch (IOException e) {
114             throw new RuntimeException(
115                     "Should never happen, ShellOutputReceiverStream.close is a no-op");
116         }
117         return result;
118     }
119 
120     /** {@inheritDoc} */
121     @Override
loadFilter(String binaryOnHost)122     public String loadFilter(String binaryOnHost) {
123         try {
124             CLog.i("Loading filter from file for key: '%s'", getTestFilterKey());
125             String filterFileName = String.format("%s%s", binaryOnHost, FILTER_EXTENSION);
126             File filterFile = new File(filterFileName);
127             if (filterFile.exists()) {
128                 CommandResult cmdResult =
129                         executeHostCommand(String.format("cat %s", filterFileName));
130                 String content = cmdResult.getStdout();
131                 if (content != null && !content.isEmpty()) {
132                     JSONObject filter = new JSONObject(content);
133                     String key = getTestFilterKey();
134                     JSONObject filterObject = filter.getJSONObject(key);
135                     return filterObject.getString("filter");
136                 }
137                 CLog.e("Error with content of the filter file %s: %s", filterFile, content);
138             } else {
139                 CLog.e("Filter file %s not found", filterFile);
140             }
141         } catch (JSONException e) {
142             CLog.e(e);
143         }
144         return null;
145     }
146 
147     /**
148      * Run the given gtest binary
149      *
150      * @param resultParser the test run output parser
151      * @param fullPath absolute file system path to gtest binary
152      * @param flags gtest execution flags
153      */
runTest( final IShellOutputReceiver resultParser, final String fullPath, final String flags)154     private void runTest(
155             final IShellOutputReceiver resultParser, final String fullPath, final String flags) {
156         try {
157             for (String cmd : getBeforeTestCmd()) {
158                 CommandResult result = executeHostCommand(cmd);
159                 if (!result.getStatus().equals(CommandStatus.SUCCESS)) {
160                     throw new RuntimeException(
161                             "'Before test' command failed: " + result.getStderr());
162                 }
163             }
164 
165             long maxTestTimeMs = getMaxTestTimeMs();
166             String cmd = getGTestCmdLine(fullPath, flags);
167             CommandResult testResult = executeHostGTestCommand(cmd, maxTestTimeMs, resultParser);
168             // TODO: Switch throwing exceptions to use ITestInvocation.testRunFailed
169             switch (testResult.getStatus()) {
170                 case FAILED:
171                     // Check the command exit code. If it's 1, then this is just a red herring;
172                     // gtest returns 1 when a test fails.
173                     final Integer exitCode = testResult.getExitCode();
174                     if (exitCode == null || exitCode != 1) {
175                         throw new RuntimeException(
176                                 String.format("Command run failed with exit code %s", exitCode));
177                     }
178                     break;
179                 case TIMED_OUT:
180                     throw new RuntimeException(
181                             String.format("Command run timed out after %d ms", maxTestTimeMs));
182                 case EXCEPTION:
183                     throw new RuntimeException("Command run failed with exception");
184                 default:
185                     break;
186             }
187         } finally {
188             resultParser.flush();
189         }
190         // Execute the host command if nothing failed badly before.
191         for (String cmd : getAfterTestCmd()) {
192             CommandResult result = executeHostCommand(cmd);
193             if (!result.getStatus().equals(CommandStatus.SUCCESS)) {
194                 throw new RuntimeException("'After test' command failed: " + result.getStderr());
195             }
196         }
197     }
198 
199     /** {@inheritDoc} */
200     @Override
run(TestInformation testInfo, ITestInvocationListener listener)201     public void run(TestInformation testInfo, ITestInvocationListener listener)
202             throws DeviceNotAvailableException { // DNAE is part of IRemoteTest.
203         // Get testcases directory using the key HOST_LINKED_DIR first.
204         // If the directory is null, then get testcase directory from getTestDir() since *TS will
205         // invoke setTestDir().
206         List<File> scanDirs = new ArrayList<>();
207         File hostLinkedDir = mBuildInfo.getFile(BuildInfoFileKey.HOST_LINKED_DIR);
208         if (hostLinkedDir != null) {
209             scanDirs.add(hostLinkedDir);
210         }
211         File testsDir = ((DeviceBuildInfo) mBuildInfo).getTestsDir();
212         if (testsDir != null) {
213             scanDirs.add(testsDir);
214         }
215 
216         String moduleName = getTestModule();
217         File gTestFile = null;
218         try {
219             gTestFile = FileUtil.findFile(moduleName, mAbi, scanDirs.toArray(new File[] {}));
220         } catch (IOException e) {
221             throw new RuntimeException(e);
222         }
223 
224         if (gTestFile == null || gTestFile.isDirectory()) {
225             // If we ended up here we most likely failed to find the proper file as is, so we
226             // search for it with a potential suffix (which is allowed).
227             try {
228                 File byBaseName =
229                         FileUtil.findFile(moduleName + ".*", mAbi, scanDirs.toArray(new File[] {}));
230                 if (byBaseName != null && byBaseName.isFile()) {
231                     gTestFile = byBaseName;
232                 }
233             } catch (IOException e) {
234                 throw new RuntimeException(e);
235             }
236         }
237 
238         if (gTestFile == null) {
239             throw new RuntimeException(
240                     String.format(
241                             "Fail to find native test %s in directory %s.", moduleName, scanDirs));
242         }
243 
244         if (!gTestFile.canExecute()) {
245             throw new RuntimeException(
246                     String.format("%s is not executable!", gTestFile.getAbsolutePath()));
247         }
248 
249         listener = getGTestListener(listener);
250         // TODO: Need to support XML test output based on isEnableXmlOutput
251         IShellOutputReceiver resultParser = createResultParser(gTestFile.getName(), listener);
252         String flags = getAllGTestFlags(gTestFile.getName());
253         CLog.i("Running gtest %s %s", gTestFile.getName(), flags);
254         String filePath = gTestFile.getAbsolutePath();
255         runTest(resultParser, filePath, flags);
256     }
257 }
258