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