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 package com.android.tradefed.invoker.sandbox; 17 18 import com.android.annotations.VisibleForTesting; 19 import com.android.tradefed.build.BuildRetrievalError; 20 import com.android.tradefed.config.Configuration; 21 import com.android.tradefed.config.ConfigurationFactory; 22 import com.android.tradefed.config.IConfiguration; 23 import com.android.tradefed.config.IConfigurationFactory; 24 import com.android.tradefed.device.DeviceNotAvailableException; 25 import com.android.tradefed.device.ITestDevice; 26 import com.android.tradefed.device.StubDevice; 27 import com.android.tradefed.error.HarnessRuntimeException; 28 import com.android.tradefed.invoker.IInvocationContext; 29 import com.android.tradefed.invoker.IRescheduler; 30 import com.android.tradefed.invoker.InvocationExecution; 31 import com.android.tradefed.invoker.TestInformation; 32 import com.android.tradefed.invoker.TestInvocation.Stage; 33 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 34 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 35 import com.android.tradefed.invoker.tracing.CloseableTraceScope; 36 import com.android.tradefed.log.ITestLogger; 37 import com.android.tradefed.log.LogUtil.CLog; 38 import com.android.tradefed.result.ITestInvocationListener; 39 import com.android.tradefed.result.error.InfraErrorIdentifier; 40 import com.android.tradefed.sandbox.ISandbox; 41 import com.android.tradefed.sandbox.SandboxInvocationRunner; 42 import com.android.tradefed.sandbox.SandboxOptions; 43 import com.android.tradefed.targetprep.BuildError; 44 import com.android.tradefed.targetprep.ITargetPreparer; 45 import com.android.tradefed.targetprep.TargetSetupError; 46 import com.android.tradefed.util.IRunUtil; 47 import com.android.tradefed.util.QuotationAwareTokenizer; 48 import com.android.tradefed.util.RunUtil; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 53 /** 54 * Version of {@link InvocationExecution} for the parent invocation special actions when running a 55 * sandbox. 56 */ 57 public class ParentSandboxInvocationExecution extends InvocationExecution { 58 59 private SandboxSetupThread setupThread; 60 private TestInformation mTestInfo; 61 62 @Override fetchBuild( TestInformation testInfo, IConfiguration config, IRescheduler rescheduler, ITestInvocationListener listener)63 public boolean fetchBuild( 64 TestInformation testInfo, 65 IConfiguration config, 66 IRescheduler rescheduler, 67 ITestInvocationListener listener) 68 throws DeviceNotAvailableException, BuildRetrievalError { 69 mTestInfo = testInfo; 70 if (!testInfo.getContext().getBuildInfos().isEmpty()) { 71 CLog.d( 72 "Context already contains builds: %s. Skipping download as we are in " 73 + "sandbox-test-mode.", 74 testInfo.getContext().getBuildInfos()); 75 return true; 76 } 77 78 SandboxFetchThread fetchThread = null; 79 if (getSandboxOptions(config).shouldUseSplitDiscovery()) { 80 fetchThread = 81 new SandboxFetchThread( 82 Thread.currentThread().getThreadGroup(), testInfo, config); 83 fetchThread.start(); 84 } 85 boolean res = false; 86 try { 87 res = super.fetchBuild(testInfo, config, rescheduler, listener); 88 } catch (DeviceNotAvailableException | BuildRetrievalError | RuntimeException e) { 89 if (fetchThread != null) { 90 fetchThread.interrupt(); 91 } 92 SandboxInvocationRunner.teardownSandbox(config); 93 throw e; 94 } 95 if (getSandboxOptions(config).shouldUseSplitDiscovery()) { 96 Throwable e = null; 97 try { 98 fetchThread.join(); 99 e = fetchThread.error; 100 if (e != null) { 101 if (e instanceof BuildRetrievalError) { 102 throw (BuildRetrievalError) e; 103 } else { 104 throw new HarnessRuntimeException( 105 e.getMessage(), e, InfraErrorIdentifier.SANDBOX_SETUP_ERROR); 106 } 107 } 108 } catch (InterruptedException execError) { 109 SandboxInvocationRunner.teardownSandbox(config); 110 throw new BuildRetrievalError( 111 execError.getMessage(), 112 execError, 113 InfraErrorIdentifier.SANDBOX_SETUP_ERROR); 114 } 115 if (res && e == null) { 116 getSandbox(config).discoverTests(testInfo.getContext(), config, listener); 117 } 118 } 119 return res; 120 } 121 122 /** {@inheritDoc} */ 123 @Override getTargetPreparersToRun( IConfiguration config, String deviceName)124 protected List<ITargetPreparer> getTargetPreparersToRun( 125 IConfiguration config, String deviceName) { 126 return new ArrayList<>(); 127 } 128 129 /** {@inheritDoc} */ 130 @Override getLabPreparersToRun(IConfiguration config, String deviceName)131 protected List<ITargetPreparer> getLabPreparersToRun(IConfiguration config, String deviceName) { 132 List<ITargetPreparer> preparersToRun = new ArrayList<>(); 133 preparersToRun.addAll(config.getDeviceConfigByName(deviceName).getLabPreparers()); 134 return preparersToRun; 135 } 136 137 @Override doSetup(TestInformation testInfo, IConfiguration config, ITestLogger listener)138 public void doSetup(TestInformation testInfo, IConfiguration config, ITestLogger listener) 139 throws TargetSetupError, BuildError, DeviceNotAvailableException { 140 // TODO address the situation where multi-target preparers are configured 141 // (they will be run by both the parent and sandbox if configured) 142 boolean parallelSetup = getSandboxOptions(config).shouldParallelSetup(); 143 try { 144 super.doSetup(testInfo, config, listener); 145 } catch (DeviceNotAvailableException | TargetSetupError | BuildError | RuntimeException e) { 146 if (parallelSetup) { 147 // Join and clean up since run won't be called. 148 try { 149 setupThread.join(); 150 } catch (InterruptedException ie) { 151 // Ignore 152 CLog.e(ie); 153 } 154 SandboxInvocationRunner.teardownSandbox(config); 155 } 156 throw e; 157 } 158 } 159 160 @Override doTeardown( TestInformation testInfo, IConfiguration config, ITestLogger logger, Throwable exception)161 public void doTeardown( 162 TestInformation testInfo, 163 IConfiguration config, 164 ITestLogger logger, 165 Throwable exception) 166 throws Throwable { 167 // TODO address the situation where multi-target preparers are configured 168 // (they will be run by both the parent and sandbox if configured) 169 super.doTeardown(testInfo, config, logger, exception); 170 } 171 172 @Override doCleanUp(IInvocationContext context, IConfiguration config, Throwable exception)173 public void doCleanUp(IInvocationContext context, IConfiguration config, Throwable exception) { 174 try { 175 super.doCleanUp(context, config, exception); 176 } finally { 177 // Always clean up sandbox when we get to the end. 178 SandboxInvocationRunner.teardownSandbox(config); 179 } 180 } 181 182 /** {@inheritDoc} */ 183 @Override runDevicePreInvocationSetup( IInvocationContext context, IConfiguration config, ITestLogger logger)184 public void runDevicePreInvocationSetup( 185 IInvocationContext context, IConfiguration config, ITestLogger logger) 186 throws DeviceNotAvailableException, TargetSetupError { 187 if (shouldRunDeviceSpecificSetup(config)) { 188 boolean parallelSetup = getSandboxOptions(config).shouldParallelSetup(); 189 if (parallelSetup) { 190 setupThread = 191 new SandboxSetupThread( 192 Thread.currentThread().getThreadGroup(), 193 mTestInfo, 194 config, 195 (ITestInvocationListener) logger); 196 setupThread.start(); 197 } 198 try { 199 super.runDevicePreInvocationSetup(context, config, logger); 200 } catch (DeviceNotAvailableException | TargetSetupError | RuntimeException e) { 201 if (parallelSetup) { 202 // Join and clean up since run won't be called. 203 try { 204 setupThread.join(); 205 } catch (InterruptedException ie) { 206 // Ignore 207 CLog.e(ie); 208 } 209 SandboxInvocationRunner.teardownSandbox(config); 210 } 211 throw e; 212 } 213 } 214 } 215 216 /** {@inheritDoc} */ 217 @Override runDevicePostInvocationTearDown( IInvocationContext context, IConfiguration config, Throwable exception)218 public void runDevicePostInvocationTearDown( 219 IInvocationContext context, IConfiguration config, Throwable exception) { 220 if (shouldRunDeviceSpecificSetup(config)) { 221 super.runDevicePostInvocationTearDown(context, config, exception); 222 } 223 } 224 225 @Override runTests( TestInformation info, IConfiguration config, ITestInvocationListener listener)226 public void runTests( 227 TestInformation info, IConfiguration config, ITestInvocationListener listener) 228 throws Throwable { 229 try (CloseableTraceScope ignore = new CloseableTraceScope("prepareAndRunSandbox")) { 230 prepareAndRunSandbox(info, config, listener); 231 } 232 } 233 234 @Override reportLogs(ITestDevice device, ITestLogger logger, Stage stage)235 public void reportLogs(ITestDevice device, ITestLogger logger, Stage stage) { 236 // If it's a test logcat do not report it, the subprocess will take care of it. 237 if (Stage.TEST.equals(stage)) { 238 return; 239 } 240 super.reportLogs(device, logger, stage); 241 } 242 243 /** Returns the {@link IConfigurationFactory} used to created configurations. */ 244 @VisibleForTesting getFactory()245 protected IConfigurationFactory getFactory() { 246 return ConfigurationFactory.getInstance(); 247 } 248 249 @VisibleForTesting getRunUtil()250 protected IRunUtil getRunUtil() { 251 return RunUtil.getDefault(); 252 } 253 254 /** Returns the result status of running the sandbox. */ 255 @VisibleForTesting prepareAndRunSandbox( TestInformation info, IConfiguration config, ITestInvocationListener listener)256 protected boolean prepareAndRunSandbox( 257 TestInformation info, IConfiguration config, ITestInvocationListener listener) 258 throws Throwable { 259 // Stop background logcat in parent process during the sandbox 260 for (String deviceName : info.getContext().getDeviceConfigNames()) { 261 if (!(info.getContext().getDevice(deviceName).getIDevice() instanceof StubDevice)) { 262 info.getContext().getDevice(deviceName).stopLogcat(); 263 CLog.i( 264 "Done stopping logcat for %s", 265 info.getContext().getDevice(deviceName).getSerialNumber()); 266 } 267 } 268 269 if (getSandboxOptions(config).shouldParallelSetup()) { 270 long startTime = System.currentTimeMillis(); 271 try { 272 setupThread.join(); 273 } finally { 274 // Only track as overhead the time that setup runs longer 275 // than other actions (critical path) 276 InvocationMetricLogger.addInvocationPairMetrics( 277 InvocationMetricKey.DYNAMIC_FILE_RESOLVER_PAIR, 278 startTime, 279 System.currentTimeMillis()); 280 } 281 if (setupThread.error != null) { 282 CLog.e("An exception occurred during parallel setup."); 283 throw setupThread.error; 284 } 285 return SandboxInvocationRunner.runSandbox(info, config, listener); 286 } 287 return SandboxInvocationRunner.prepareAndRun(info, config, listener); 288 } 289 290 /** 291 * Whether or not to run the device pre invocation setup or not. 292 */ shouldRunDeviceSpecificSetup(IConfiguration config)293 private boolean shouldRunDeviceSpecificSetup(IConfiguration config) { 294 SandboxOptions options = getSandboxOptions(config); 295 if (options != null && options.startAvdInParent()) { 296 return true; 297 } 298 return false; 299 } 300 getSandboxOptions(IConfiguration config)301 private SandboxOptions getSandboxOptions(IConfiguration config) { 302 return (SandboxOptions) 303 config.getConfigurationObject(Configuration.SANBOX_OPTIONS_TYPE_NAME); 304 } 305 getSandbox(IConfiguration config)306 private ISandbox getSandbox(IConfiguration config) { 307 return (ISandbox) config.getConfigurationObject(Configuration.SANDBOX_TYPE_NAME); 308 } 309 310 private class SandboxFetchThread extends Thread { 311 private final TestInformation info; 312 private final IConfiguration config; 313 314 // The error that might be returned by the setup 315 public Throwable error; 316 SandboxFetchThread( ThreadGroup currentGroup, TestInformation info, IConfiguration config)317 public SandboxFetchThread( 318 ThreadGroup currentGroup, TestInformation info, IConfiguration config) { 319 super(currentGroup, "SandboxFetchThread"); 320 setDaemon(true); 321 this.info = info; 322 this.config = config; 323 } 324 325 @Override run()326 public void run() { 327 try { 328 getSandbox(config) 329 .fetchSandboxExtraArtifacts( 330 info.getContext(), 331 config, 332 QuotationAwareTokenizer.tokenizeLine( 333 config.getCommandLine(), 334 /** no logging */ 335 false)); 336 } catch (Throwable e) { 337 error = e; 338 } 339 } 340 } 341 342 private class SandboxSetupThread extends Thread { 343 344 private final TestInformation info; 345 private final IConfiguration config; 346 private final ITestInvocationListener listener; 347 // The error that might be returned by the setup 348 public Throwable error; 349 SandboxSetupThread( ThreadGroup currentGroup, TestInformation info, IConfiguration config, ITestInvocationListener listener)350 public SandboxSetupThread( 351 ThreadGroup currentGroup, 352 TestInformation info, 353 IConfiguration config, 354 ITestInvocationListener listener) { 355 super(currentGroup, "SandboxSetupThread"); 356 setDaemon(true); 357 this.info = info; 358 this.config = config; 359 this.listener = listener; 360 } 361 362 @Override run()363 public void run() { 364 try { 365 SandboxInvocationRunner.prepareSandbox(info, config, listener); 366 } catch (Throwable e) { 367 error = e; 368 } 369 } 370 } 371 } 372