1 /* 2 * Copyright (C) 2017 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.sandbox; 17 18 import com.android.annotations.VisibleForTesting; 19 import com.android.tradefed.command.CommandOptions; 20 import com.android.tradefed.config.ArgsOptionParser; 21 import com.android.tradefed.config.Configuration; 22 import com.android.tradefed.config.ConfigurationException; 23 import com.android.tradefed.config.ConfigurationFactory; 24 import com.android.tradefed.config.ConfigurationXmlParserSettings; 25 import com.android.tradefed.config.GlobalConfiguration; 26 import com.android.tradefed.config.IConfiguration; 27 import com.android.tradefed.config.IConfigurationFactory; 28 import com.android.tradefed.config.IDeviceConfiguration; 29 import com.android.tradefed.config.IGlobalConfiguration; 30 import com.android.tradefed.config.proxy.AutomatedReporters; 31 import com.android.tradefed.device.DeviceSelectionOptions; 32 import com.android.tradefed.device.IDeviceSelection.BaseDeviceType; 33 import com.android.tradefed.device.ManagedTestDeviceFactory; 34 import com.android.tradefed.invoker.IInvocationContext; 35 import com.android.tradefed.invoker.InvocationContext; 36 import com.android.tradefed.invoker.RemoteInvocationExecution; 37 import com.android.tradefed.invoker.TestInformation; 38 import com.android.tradefed.invoker.logger.CurrentInvocation; 39 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 40 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 41 import com.android.tradefed.invoker.proto.InvocationContext.Context; 42 import com.android.tradefed.invoker.tracing.CloseableTraceScope; 43 import com.android.tradefed.log.ITestLogger; 44 import com.android.tradefed.log.LogUtil.CLog; 45 import com.android.tradefed.result.FileInputStreamSource; 46 import com.android.tradefed.result.ITestInvocationListener; 47 import com.android.tradefed.result.InputStreamSource; 48 import com.android.tradefed.result.LogDataType; 49 import com.android.tradefed.result.proto.StreamProtoReceiver; 50 import com.android.tradefed.result.proto.StreamProtoResultReporter; 51 import com.android.tradefed.sandbox.SandboxConfigDump.DumpCmd; 52 import com.android.tradefed.service.TradefedFeatureServer; 53 import com.android.tradefed.util.CommandResult; 54 import com.android.tradefed.util.CommandStatus; 55 import com.android.tradefed.util.FileUtil; 56 import com.android.tradefed.util.IRunUtil; 57 import com.android.tradefed.util.IRunUtil.EnvPriority; 58 import com.android.tradefed.util.PrettyPrintDelimiter; 59 import com.android.tradefed.util.QuotationAwareTokenizer; 60 import com.android.tradefed.util.RunUtil; 61 import com.android.tradefed.util.StreamUtil; 62 import com.android.tradefed.util.SubprocessExceptionParser; 63 import com.android.tradefed.util.SubprocessTestResultsParser; 64 import com.android.tradefed.util.SystemUtil; 65 import com.android.tradefed.util.keystore.IKeyStoreClient; 66 import com.android.tradefed.util.keystore.KeyStoreException; 67 68 import com.google.common.base.Joiner; 69 70 import java.io.File; 71 import java.io.FileOutputStream; 72 import java.io.IOException; 73 import java.io.PrintWriter; 74 import java.lang.reflect.InvocationTargetException; 75 import java.lang.reflect.Method; 76 import java.util.ArrayList; 77 import java.util.Arrays; 78 import java.util.HashSet; 79 import java.util.LinkedHashSet; 80 import java.util.List; 81 import java.util.Map; 82 import java.util.Map.Entry; 83 import java.util.Set; 84 85 /** 86 * Sandbox container that can run a Trade Federation invocation. TODO: Allow Options to be passed to 87 * the sandbox. 88 */ 89 public class TradefedSandbox implements ISandbox { 90 91 private static final String SANDBOX_PREFIX = "sandbox-"; 92 public static final String SANDBOX_ENABLED = "SANDBOX_ENABLED"; 93 94 private static final String SANDBOX_JVM_OPTIONS_ENV_VAR_KEY = "TF_SANDBOX_JVM_OPTIONS"; 95 96 private File mStdoutFile = null; 97 private File mStderrFile = null; 98 private File mHeapDump = null; 99 100 private File mSandboxTmpFolder = null; 101 private File mRootFolder = null; 102 private File mGlobalConfig = null; 103 private File mSerializedContext = null; 104 private File mSerializedConfiguration = null; 105 private File mSerializedTestConfig = null; 106 107 private SubprocessTestResultsParser mEventParser = null; 108 private StreamProtoReceiver mProtoReceiver = null; 109 110 private IRunUtil mRunUtil; 111 112 @VisibleForTesting getJava()113 protected String getJava() { 114 return SystemUtil.getRunningJavaBinaryPath().getAbsolutePath(); 115 } 116 117 @Override run(TestInformation info, IConfiguration config, ITestLogger logger)118 public CommandResult run(TestInformation info, IConfiguration config, ITestLogger logger) 119 throws Throwable { 120 List<String> mCmdArgs = new ArrayList<>(); 121 mCmdArgs.add(getJava()); 122 mCmdArgs.add(String.format("-Djava.io.tmpdir=%s", mSandboxTmpFolder.getAbsolutePath())); 123 mCmdArgs.add(String.format("-DTF_JAR_DIR=%s", mRootFolder.getAbsolutePath())); 124 // Setup heap dump collection 125 try { 126 mHeapDump = FileUtil.createTempDir("heap-dump", getWorkFolder()); 127 mCmdArgs.add("-XX:+HeapDumpOnOutOfMemoryError"); 128 mCmdArgs.add(String.format("-XX:HeapDumpPath=%s", mHeapDump.getAbsolutePath())); 129 } catch (IOException e) { 130 CLog.e(e); 131 } 132 SandboxOptions sandboxOptions = getSandboxOptions(config); 133 mCmdArgs.addAll(sandboxOptions.getJavaOptions()); 134 if (System.getenv(SANDBOX_JVM_OPTIONS_ENV_VAR_KEY) != null) { 135 mCmdArgs.addAll( 136 Arrays.asList(System.getenv(SANDBOX_JVM_OPTIONS_ENV_VAR_KEY).split(","))); 137 } 138 mCmdArgs.add("-cp"); 139 mCmdArgs.add(createClasspath(mRootFolder)); 140 mCmdArgs.add(TradefedSandboxRunner.class.getCanonicalName()); 141 mCmdArgs.add(mSerializedContext.getAbsolutePath()); 142 mCmdArgs.add(mSerializedConfiguration.getAbsolutePath()); 143 if (mProtoReceiver != null) { 144 mCmdArgs.add("--" + StreamProtoResultReporter.PROTO_REPORT_PORT_OPTION); 145 mCmdArgs.add(Integer.toString(mProtoReceiver.getSocketServerPort())); 146 } else { 147 mCmdArgs.add("--subprocess-report-port"); 148 mCmdArgs.add(Integer.toString(mEventParser.getSocketServerPort())); 149 } 150 if (config.getCommandOptions().shouldUseSandboxTestMode()) { 151 // In test mode, re-add the --use-sandbox to trigger a sandbox run again in the process 152 mCmdArgs.add("--" + CommandOptions.USE_SANDBOX); 153 } 154 if (sandboxOptions.startAvdInParent()) { 155 Set<String> notifyAsNative = new LinkedHashSet<String>(); 156 for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) { 157 if (deviceConfig.getDeviceRequirements().gceDeviceRequested()) { 158 // Turn off the gce-device option and force the serial instead to use the 159 // started virtual device. 160 String deviceName = 161 (config.getDeviceConfig().size() > 1) 162 ? String.format("{%s}", deviceConfig.getDeviceName()) 163 : ""; 164 mCmdArgs.add(String.format("--%sno-gce-device", deviceName)); 165 mCmdArgs.add(String.format("--%sserial", deviceName)); 166 mCmdArgs.add( 167 info.getContext() 168 .getDevice(deviceConfig.getDeviceName()) 169 .getSerialNumber()); 170 // If we are using the device-type selector, override it 171 if (DeviceSelectionOptions.DeviceRequestedType.GCE_DEVICE.equals( 172 ((DeviceSelectionOptions) deviceConfig.getDeviceRequirements()) 173 .getDeviceTypeRequested())) { 174 mCmdArgs.add(String.format("--%sdevice-type", deviceName)); 175 mCmdArgs.add( 176 DeviceSelectionOptions.DeviceRequestedType.EXISTING_DEVICE.name()); 177 } 178 if (BaseDeviceType.NATIVE_DEVICE.equals( 179 deviceConfig.getDeviceRequirements().getBaseDeviceTypeRequested())) { 180 notifyAsNative.add( 181 info.getContext() 182 .getDevice(deviceConfig.getDeviceName()) 183 .getSerialNumber()); 184 } 185 } 186 } 187 if (!notifyAsNative.isEmpty()) { 188 mRunUtil.setEnvVariable( 189 ManagedTestDeviceFactory.NOTIFY_AS_NATIVE, 190 Joiner.on(",").join(notifyAsNative)); 191 } 192 } 193 194 // Remove a bit of timeout to account for parent overhead 195 long timeout = Math.max(config.getCommandOptions().getInvocationTimeout() - 120000L, 0); 196 // Allow interruption, subprocess should handle signals itself 197 mRunUtil.allowInterrupt(true); 198 CommandResult result = null; 199 RuntimeException interruptedException = null; 200 try { 201 result = 202 mRunUtil.runTimedCmdWithInput( 203 timeout, /*input*/ 204 null, 205 mStdoutFile, 206 mStderrFile, 207 mCmdArgs.toArray(new String[0])); 208 } catch (RuntimeException interrupted) { 209 CLog.e("Sandbox runtimedCmd threw an exception"); 210 CLog.e(interrupted); 211 interruptedException = interrupted; 212 result = new CommandResult(CommandStatus.EXCEPTION); 213 result.setStdout(StreamUtil.getStackTrace(interrupted)); 214 } 215 216 boolean failedStatus = false; 217 String stderrText; 218 try { 219 stderrText = FileUtil.readStringFromFile(mStderrFile); 220 } catch (IOException e) { 221 stderrText = "Could not read the stderr output from process."; 222 } 223 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 224 failedStatus = true; 225 result.setStderr(stderrText); 226 } 227 228 try { 229 boolean joinResult = false; 230 long waitTime = getSandboxOptions(config).getWaitForEventsTimeout(); 231 try (CloseableTraceScope ignored = 232 new CloseableTraceScope( 233 InvocationMetricKey.invocation_events_processing.toString())) { 234 if (mProtoReceiver != null) { 235 joinResult = mProtoReceiver.joinReceiver(waitTime); 236 } else { 237 joinResult = mEventParser.joinReceiver(waitTime); 238 } 239 } 240 if (interruptedException != null) { 241 throw interruptedException; 242 } 243 if (!joinResult) { 244 if (!failedStatus) { 245 result.setStatus(CommandStatus.EXCEPTION); 246 } 247 result.setStderr( 248 String.format( 249 "%s:\n%s", 250 SubprocessExceptionParser.EVENT_THREAD_JOIN, stderrText)); 251 } 252 PrettyPrintDelimiter.printStageDelimiter( 253 String.format( 254 "Execution of the tests occurred in the sandbox, you can find its logs " 255 + "under the name pattern '%s*'", 256 SANDBOX_PREFIX)); 257 } finally { 258 if (mProtoReceiver != null) { 259 mProtoReceiver.completeModuleEvents(); 260 } 261 try (InputStreamSource contextFile = new FileInputStreamSource(mSerializedContext)) { 262 logger.testLog("sandbox-context", LogDataType.PB, contextFile); 263 } 264 // Log stdout and stderr 265 if (mStdoutFile != null) { 266 try (InputStreamSource sourceStdOut = new FileInputStreamSource(mStdoutFile)) { 267 logger.testLog("sandbox-stdout", LogDataType.HARNESS_STD_LOG, sourceStdOut); 268 } 269 } 270 try (InputStreamSource sourceStdErr = new FileInputStreamSource(mStderrFile)) { 271 logger.testLog("sandbox-stderr", LogDataType.HARNESS_STD_LOG, sourceStdErr); 272 } 273 // Collect heap dump if any 274 logAndCleanHeapDump(mHeapDump, logger); 275 mHeapDump = null; 276 } 277 278 if (result.getExitCode() != null) { 279 // Log the exit code 280 InvocationMetricLogger.addInvocationMetrics( 281 InvocationMetricKey.SANDBOX_EXIT_CODE, result.getExitCode()); 282 } 283 if (mProtoReceiver != null && mProtoReceiver.hasInvocationFailed()) { 284 // If an invocation failed has already been reported, skip the logic below to report it 285 // again. 286 return result; 287 } 288 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 289 CLog.e( 290 "Sandbox finished with status: %s and exit code: %s", 291 result.getStatus(), result.getExitCode()); 292 SubprocessExceptionParser.handleStderrException(result); 293 } 294 return result; 295 } 296 297 @Override prepareEnvironment( IInvocationContext context, IConfiguration config, ITestInvocationListener listener)298 public Exception prepareEnvironment( 299 IInvocationContext context, IConfiguration config, ITestInvocationListener listener) { 300 long startTime = System.currentTimeMillis(); 301 try { 302 // Create our temp directories. 303 try { 304 mStdoutFile = 305 FileUtil.createTempFile("stdout_subprocess_", ".log", getWorkFolder()); 306 mStderrFile = 307 FileUtil.createTempFile("stderr_subprocess_", ".log", getWorkFolder()); 308 mSandboxTmpFolder = FileUtil.createTempDir("tf-container", getWorkFolder()); 309 } catch (IOException e) { 310 return e; 311 } 312 // Unset the current global environment 313 mRunUtil = createRunUtil(); 314 mRunUtil.unsetEnvVariable(GlobalConfiguration.GLOBAL_CONFIG_VARIABLE); 315 mRunUtil.unsetEnvVariable(GlobalConfiguration.GLOBAL_CONFIG_SERVER_CONFIG_VARIABLE); 316 mRunUtil.unsetEnvVariable(AutomatedReporters.PROTO_REPORTING_PORT); 317 // Handle feature server 318 mRunUtil.unsetEnvVariable(RemoteInvocationExecution.START_FEATURE_SERVER); 319 mRunUtil.unsetEnvVariable(TradefedFeatureServer.TF_SERVICE_PORT); 320 mRunUtil.setEnvVariablePriority(EnvPriority.SET); 321 mRunUtil.setEnvVariable( 322 TradefedFeatureServer.TF_SERVICE_PORT, 323 Integer.toString(TradefedFeatureServer.getPort())); 324 // Mark subprocess for sandbox 325 mRunUtil.setEnvVariable(SANDBOX_ENABLED, "true"); 326 327 if (getSandboxOptions(config).shouldEnableDebugThread()) { 328 mRunUtil.setEnvVariable(TradefedSandboxRunner.DEBUG_THREAD_KEY, "true"); 329 } 330 for (Entry<String, String> envEntry : 331 getSandboxOptions(config).getEnvVariables().entrySet()) { 332 mRunUtil.setEnvVariable(envEntry.getKey(), envEntry.getValue()); 333 } 334 if (config.getConfigurationDescription() 335 .getMetaData(TradefedFeatureServer.SERVER_REFERENCE) 336 != null) { 337 mRunUtil.setEnvVariable( 338 TradefedFeatureServer.SERVER_REFERENCE, 339 config.getConfigurationDescription() 340 .getAllMetaData() 341 .getUniqueMap() 342 .get(TradefedFeatureServer.SERVER_REFERENCE)); 343 } 344 345 try { 346 mRootFolder = 347 getTradefedSandboxEnvironment( 348 context, 349 config, 350 listener, 351 QuotationAwareTokenizer.tokenizeLine( 352 config.getCommandLine(), 353 /** no logging */ 354 false)); 355 } catch (Exception e) { 356 return e; 357 } 358 359 PrettyPrintDelimiter.printStageDelimiter("Sandbox Configuration Preparation"); 360 // Prepare the configuration 361 Exception res = prepareConfiguration(context, config, listener); 362 if (res != null) { 363 return res; 364 } 365 // Prepare the context 366 try (CloseableTraceScope ignored = new CloseableTraceScope("prepareContext")) { 367 mSerializedContext = prepareContext(context, config); 368 } catch (IOException e) { 369 return e; 370 } 371 } finally { 372 if (!getSandboxOptions(config).shouldParallelSetup()) { 373 InvocationMetricLogger.addInvocationPairMetrics( 374 InvocationMetricKey.DYNAMIC_FILE_RESOLVER_PAIR, 375 startTime, 376 System.currentTimeMillis()); 377 } 378 } 379 return null; 380 } 381 382 @Override tearDown()383 public void tearDown() { 384 StreamUtil.close(mEventParser); 385 StreamUtil.close(mProtoReceiver); 386 FileUtil.deleteFile(mStdoutFile); 387 FileUtil.deleteFile(mStderrFile); 388 FileUtil.recursiveDelete(mSandboxTmpFolder); 389 FileUtil.deleteFile(mSerializedContext); 390 FileUtil.deleteFile(mSerializedConfiguration); 391 FileUtil.deleteFile(mGlobalConfig); 392 FileUtil.deleteFile(mSerializedTestConfig); 393 } 394 395 @Override getTradefedSandboxEnvironment( IInvocationContext context, IConfiguration nonVersionedConfig, ITestLogger logger, String[] args)396 public File getTradefedSandboxEnvironment( 397 IInvocationContext context, 398 IConfiguration nonVersionedConfig, 399 ITestLogger logger, 400 String[] args) 401 throws Exception { 402 SandboxOptions options = getSandboxOptions(nonVersionedConfig); 403 // Check that we have no args conflicts. 404 if (options.getSandboxTfDirectory() != null && options.getSandboxBuildId() != null) { 405 throw new ConfigurationException( 406 String.format( 407 "Sandbox options %s and %s cannot be set at the same time", 408 SandboxOptions.TF_LOCATION, SandboxOptions.SANDBOX_BUILD_ID)); 409 } 410 411 if (options.getSandboxTfDirectory() != null) { 412 return options.getSandboxTfDirectory(); 413 } 414 String tfDir = System.getProperty("TF_JAR_DIR"); 415 if (tfDir == null || tfDir.isEmpty()) { 416 throw new ConfigurationException( 417 "Could not read TF_JAR_DIR to get current Tradefed instance."); 418 } 419 return new File(tfDir); 420 } 421 422 /** 423 * Create a classpath based on the environment and the working directory returned by {@link 424 * #getTradefedSandboxEnvironment(IInvocationContext, IConfiguration, String[])}. 425 * 426 * @param workingDir the current working directory for the sandbox. 427 * @return The classpath to be use. 428 */ 429 @Override createClasspath(File workingDir)430 public String createClasspath(File workingDir) throws ConfigurationException { 431 // Get the classpath property. 432 String classpathStr = System.getProperty("java.class.path"); 433 if (classpathStr == null) { 434 throw new ConfigurationException( 435 "Could not find the classpath property: java.class.path"); 436 } 437 return classpathStr; 438 } 439 440 /** 441 * Prepare the {@link IConfiguration} that will be passed to the subprocess and will drive the 442 * container execution. 443 * 444 * @param context The current {@link IInvocationContext}. 445 * @param config the {@link IConfiguration} to be prepared. 446 * @param listener The current invocation {@link ITestInvocationListener}. 447 * @return an Exception if anything went wrong, null otherwise. 448 */ prepareConfiguration( IInvocationContext context, IConfiguration config, ITestInvocationListener listener)449 protected Exception prepareConfiguration( 450 IInvocationContext context, IConfiguration config, ITestInvocationListener listener) { 451 try { 452 String commandLine = config.getCommandLine(); 453 if (getSandboxOptions(config).shouldUseProtoReporter()) { 454 mProtoReceiver = 455 new StreamProtoReceiver(listener, context, false, false, SANDBOX_PREFIX); 456 // Force the child to the same mode as the parent. 457 commandLine = commandLine + " --" + SandboxOptions.USE_PROTO_REPORTER; 458 } else { 459 mEventParser = new SubprocessTestResultsParser(listener, true, context); 460 commandLine = commandLine + " --no-" + SandboxOptions.USE_PROTO_REPORTER; 461 } 462 String[] args = 463 QuotationAwareTokenizer.tokenizeLine(commandLine, /* No Logging */ false); 464 465 mGlobalConfig = dumpGlobalConfig(config, new HashSet<>()); 466 try (InputStreamSource source = new FileInputStreamSource(mGlobalConfig)) { 467 listener.testLog("sandbox-global-config", LogDataType.HARNESS_CONFIG, source); 468 } 469 DumpCmd mode = DumpCmd.RUN_CONFIG; 470 if (config.getCommandOptions().shouldUseSandboxTestMode()) { 471 mode = DumpCmd.TEST_MODE; 472 } 473 try (CloseableTraceScope ignored = new CloseableTraceScope("serialize_test_config")) { 474 mSerializedConfiguration = 475 SandboxConfigUtil.dumpConfigForVersion( 476 createClasspath(mRootFolder), 477 mRunUtil, 478 args, 479 mode, 480 mGlobalConfig, 481 false); 482 } catch (SandboxConfigurationException e) { 483 // TODO: Improve our detection of that scenario 484 CLog.e(e); 485 CLog.e("%s", args[0]); 486 if (e.getMessage().contains(String.format("Can not find local config %s", args[0])) 487 || e.getMessage() 488 .contains( 489 String.format( 490 "Could not find configuration '%s'", args[0]))) { 491 CLog.w( 492 "Child version doesn't contains '%s'. Attempting to backfill missing" 493 + " parent configuration.", 494 args[0]); 495 File parentConfig = handleChildMissingConfig(getSandboxOptions(config), args); 496 if (parentConfig != null) { 497 try (InputStreamSource source = new FileInputStreamSource(parentConfig)) { 498 listener.testLog( 499 "sandbox-parent-config", LogDataType.HARNESS_CONFIG, source); 500 } 501 if (mSerializedTestConfig != null) { 502 try (InputStreamSource source = 503 new FileInputStreamSource(mSerializedTestConfig)) { 504 listener.testLog( 505 "sandbox-test-config", LogDataType.HARNESS_CONFIG, source); 506 } 507 } 508 try { 509 mSerializedConfiguration = 510 SandboxConfigUtil.dumpConfigForVersion( 511 createClasspath(mRootFolder), 512 mRunUtil, 513 new String[] {parentConfig.getAbsolutePath()}, 514 mode, 515 mGlobalConfig, 516 false); 517 } finally { 518 FileUtil.deleteFile(parentConfig); 519 } 520 return null; 521 } 522 } 523 throw e; 524 } finally { 525 // Turn off some of the invocation level options that would be duplicated in the 526 // child sandbox subprocess. 527 config.getCommandOptions().setBugreportOnInvocationEnded(false); 528 config.getCommandOptions().setBugreportzOnInvocationEnded(false); 529 } 530 } catch (IOException | ConfigurationException e) { 531 StreamUtil.close(mEventParser); 532 StreamUtil.close(mProtoReceiver); 533 return e; 534 } 535 return null; 536 } 537 538 @VisibleForTesting createRunUtil()539 IRunUtil createRunUtil() { 540 return new RunUtil(); 541 } 542 543 /** 544 * Prepare and serialize the {@link IInvocationContext}. 545 * 546 * @param context the {@link IInvocationContext} to be prepared. 547 * @param config The {@link IConfiguration} of the sandbox. 548 * @return the serialized {@link IInvocationContext}. 549 * @throws IOException 550 */ prepareContext(IInvocationContext context, IConfiguration config)551 protected File prepareContext(IInvocationContext context, IConfiguration config) 552 throws IOException { 553 // In test mode we need to keep the context unlocked for the next layer. 554 if (config.getCommandOptions().shouldUseSandboxTestMode()) { 555 try { 556 Method unlock = InvocationContext.class.getDeclaredMethod("unlock"); 557 unlock.setAccessible(true); 558 unlock.invoke(context); 559 unlock.setAccessible(false); 560 } catch (NoSuchMethodException 561 | SecurityException 562 | IllegalAccessException 563 | IllegalArgumentException 564 | InvocationTargetException e) { 565 throw new IOException("Couldn't unlock the context.", e); 566 } 567 } 568 File protoFile = 569 FileUtil.createTempFile( 570 "context-proto", "." + LogDataType.PB.getFileExt(), mSandboxTmpFolder); 571 Context contextProto = context.toProto(); 572 contextProto.writeDelimitedTo(new FileOutputStream(protoFile)); 573 return protoFile; 574 } 575 576 /** Dump the global configuration filtered from some objects. */ dumpGlobalConfig(IConfiguration config, Set<String> exclusionPatterns)577 protected File dumpGlobalConfig(IConfiguration config, Set<String> exclusionPatterns) 578 throws IOException, ConfigurationException { 579 SandboxOptions options = getSandboxOptions(config); 580 if (options.getChildGlobalConfig() != null) { 581 IConfigurationFactory factory = ConfigurationFactory.getInstance(); 582 IGlobalConfiguration globalConfig = 583 factory.createGlobalConfigurationFromArgs( 584 new String[] {options.getChildGlobalConfig()}, new ArrayList<>()); 585 CLog.d( 586 "Using %s directly as global config without filtering", 587 options.getChildGlobalConfig()); 588 return globalConfig.cloneConfigWithFilter(); 589 } 590 return SandboxConfigUtil.dumpFilteredGlobalConfig(exclusionPatterns); 591 } 592 593 /** {@inheritDoc} */ 594 @Override createThinLauncherConfig( String[] args, IKeyStoreClient keyStoreClient, IRunUtil runUtil, File globalConfig)595 public IConfiguration createThinLauncherConfig( 596 String[] args, IKeyStoreClient keyStoreClient, IRunUtil runUtil, File globalConfig) { 597 // Default thin launcher cannot do anything, since this sandbox uses the same version as 598 // the parent version. 599 return null; 600 } 601 getSandboxOptions(IConfiguration config)602 private SandboxOptions getSandboxOptions(IConfiguration config) { 603 return (SandboxOptions) 604 config.getConfigurationObject(Configuration.SANBOX_OPTIONS_TYPE_NAME); 605 } 606 handleChildMissingConfig(SandboxOptions options, String[] args)607 private File handleChildMissingConfig(SandboxOptions options, String[] args) { 608 IConfiguration parentConfig = null; 609 File tmpParentConfig = null; 610 PrintWriter pw = null; 611 612 try { 613 if (options.dumpTestTemplate()) { 614 args = extractTestTemplate(args); 615 } 616 tmpParentConfig = FileUtil.createTempFile("parent-config", ".xml", mSandboxTmpFolder); 617 pw = new PrintWriter(tmpParentConfig); 618 IKeyStoreClient keyStoreClient = 619 GlobalConfiguration.getInstance().getKeyStoreFactory().createKeyStoreClient(); 620 parentConfig = 621 ConfigurationFactory.getInstance() 622 .createConfigurationFromArgs(args, null, keyStoreClient); 623 // Do not print deprecated options to avoid compatibility issues, and do not print 624 // unchanged options. 625 parentConfig.dumpXml(pw, new ArrayList<>(), false, false); 626 return tmpParentConfig; 627 } catch (ConfigurationException | IOException | KeyStoreException e) { 628 CLog.e("Parent doesn't understand the command either:"); 629 CLog.e(e); 630 FileUtil.deleteFile(tmpParentConfig); 631 return null; 632 } finally { 633 StreamUtil.close(pw); 634 } 635 } 636 extractTestTemplate(String[] args)637 private String[] extractTestTemplate(String[] args) throws ConfigurationException, IOException { 638 ConfigurationXmlParserSettings parserSettings = new ConfigurationXmlParserSettings(); 639 final ArgsOptionParser templateArgParser = new ArgsOptionParser(parserSettings); 640 List<String> listArgs = new ArrayList<>(Arrays.asList(args)); 641 String configArg = listArgs.remove(0); 642 List<String> leftOverCommandLine = new ArrayList<>(); 643 leftOverCommandLine.addAll(templateArgParser.parseBestEffort(listArgs, true)); 644 Map<String, String> uniqueTemplates = parserSettings.templateMap.getUniqueMap(); 645 CLog.d("Templates: %s", uniqueTemplates); 646 // We look at the "test" template since it's the usual main part of the versioned object 647 // configs. This will be improved in the future. 648 if (!uniqueTemplates.containsKey("test")) { 649 return args; 650 } 651 for (Entry<String, String> template : uniqueTemplates.entrySet()) { 652 if (!"test".equals(template.getKey())) { 653 leftOverCommandLine.add("--template:map"); 654 leftOverCommandLine.add( 655 String.format("%s=%s", template.getKey(), template.getValue())); 656 } 657 } 658 mSerializedTestConfig = 659 SandboxConfigUtil.dumpConfigForVersion( 660 createClasspath(mRootFolder), 661 mRunUtil, 662 new String[] {uniqueTemplates.get("test")}, 663 DumpCmd.STRICT_TEST, 664 mGlobalConfig, 665 false); 666 leftOverCommandLine.add("--template:map"); 667 leftOverCommandLine.add("test=" + mSerializedTestConfig.getAbsolutePath()); 668 leftOverCommandLine.add(0, configArg); 669 CLog.d("New Command line: %s", leftOverCommandLine); 670 return leftOverCommandLine.toArray(new String[0]); 671 } 672 logAndCleanHeapDump(File heapDumpDir, ITestLogger logger)673 private void logAndCleanHeapDump(File heapDumpDir, ITestLogger logger) { 674 try { 675 if (heapDumpDir == null) { 676 return; 677 } 678 if (!heapDumpDir.isDirectory()) { 679 return; 680 } 681 if (heapDumpDir.listFiles().length == 0) { 682 return; 683 } 684 for (File f : heapDumpDir.listFiles()) { 685 FileInputStreamSource fileInput = new FileInputStreamSource(f); 686 logger.testLog(f.getName(), LogDataType.HPROF, fileInput); 687 StreamUtil.cancel(fileInput); 688 } 689 } finally { 690 FileUtil.recursiveDelete(heapDumpDir); 691 } 692 } 693 getWorkFolder()694 private File getWorkFolder() { 695 return CurrentInvocation.getWorkFolder(); 696 } 697 } 698