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