• 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.invoker;
17 
18 import com.android.ddmlib.IDevice;
19 import com.android.ddmlib.Log.LogLevel;
20 import com.android.tradefed.build.BuildInfo;
21 import com.android.tradefed.build.BuildInfoKey;
22 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
23 import com.android.tradefed.build.BuildRetrievalError;
24 import com.android.tradefed.build.IBuildInfo;
25 import com.android.tradefed.build.IBuildInfo.BuildInfoProperties;
26 import com.android.tradefed.build.IBuildProvider;
27 import com.android.tradefed.build.IDeviceBuildInfo;
28 import com.android.tradefed.build.IDeviceBuildProvider;
29 import com.android.tradefed.config.GlobalConfiguration;
30 import com.android.tradefed.config.IConfiguration;
31 import com.android.tradefed.config.IDeviceConfiguration;
32 import com.android.tradefed.device.DeviceNotAvailableException;
33 import com.android.tradefed.device.ITestDevice;
34 import com.android.tradefed.device.StubDevice;
35 import com.android.tradefed.device.metric.AutoLogCollector;
36 import com.android.tradefed.device.metric.CollectorHelper;
37 import com.android.tradefed.device.metric.IMetricCollector;
38 import com.android.tradefed.device.metric.IMetricCollectorReceiver;
39 import com.android.tradefed.invoker.TestInvocation.Stage;
40 import com.android.tradefed.invoker.shard.IShardHelper;
41 import com.android.tradefed.log.ITestLogger;
42 import com.android.tradefed.log.LogUtil.CLog;
43 import com.android.tradefed.result.ITestInvocationListener;
44 import com.android.tradefed.result.ITestLoggerReceiver;
45 import com.android.tradefed.result.InputStreamSource;
46 import com.android.tradefed.result.LogDataType;
47 import com.android.tradefed.suite.checker.ISystemStatusCheckerReceiver;
48 import com.android.tradefed.targetprep.BuildError;
49 import com.android.tradefed.targetprep.IHostCleaner;
50 import com.android.tradefed.targetprep.ITargetCleaner;
51 import com.android.tradefed.targetprep.ITargetPreparer;
52 import com.android.tradefed.targetprep.TargetSetupError;
53 import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
54 import com.android.tradefed.testtype.IBuildReceiver;
55 import com.android.tradefed.testtype.IDeviceTest;
56 import com.android.tradefed.testtype.IInvocationContextReceiver;
57 import com.android.tradefed.testtype.IMultiDeviceTest;
58 import com.android.tradefed.testtype.IRemoteTest;
59 import com.android.tradefed.util.FileUtil;
60 import com.android.tradefed.util.SystemUtil;
61 import com.android.tradefed.util.SystemUtil.EnvVariable;
62 import com.android.tradefed.util.TimeUtil;
63 
64 import com.google.common.annotations.VisibleForTesting;
65 import com.google.common.base.Strings;
66 
67 import java.io.File;
68 import java.io.IOException;
69 import java.util.ArrayList;
70 import java.util.List;
71 import java.util.ListIterator;
72 import java.util.stream.Collectors;
73 
74 /**
75  * Class that describes all the invocation steps: build download, target_prep, run tests, clean up.
76  * Can be extended to override the default behavior of some steps. Order of the steps is driven by
77  * {@link TestInvocation}.
78  */
79 public class InvocationExecution implements IInvocationExecution {
80 
81     public static final String ADB_VERSION_KEY = "adb_version";
82 
83     @Override
fetchBuild( IInvocationContext context, IConfiguration config, IRescheduler rescheduler, ITestInvocationListener listener)84     public boolean fetchBuild(
85             IInvocationContext context,
86             IConfiguration config,
87             IRescheduler rescheduler,
88             ITestInvocationListener listener)
89             throws DeviceNotAvailableException, BuildRetrievalError {
90         String currentDeviceName = null;
91         try {
92             updateInvocationContext(context, config);
93             // TODO: evaluate fetching build in parallel
94             for (String deviceName : context.getDeviceConfigNames()) {
95                 currentDeviceName = deviceName;
96                 IBuildInfo info = null;
97                 ITestDevice device = context.getDevice(deviceName);
98                 IDeviceConfiguration deviceConfig = config.getDeviceConfigByName(deviceName);
99                 IBuildProvider provider = deviceConfig.getBuildProvider();
100                 // Inject the context to the provider if it can receive it
101                 if (provider instanceof IInvocationContextReceiver) {
102                     ((IInvocationContextReceiver) provider).setInvocationContext(context);
103                 }
104                 // Get the build
105                 if (provider instanceof IDeviceBuildProvider) {
106                     // Download a device build if the provider can handle it.
107                     info = ((IDeviceBuildProvider) provider).getBuild(device);
108                 } else {
109                     info = provider.getBuild();
110                 }
111                 if (info != null) {
112                     info.setDeviceSerial(device.getSerialNumber());
113                     context.addDeviceBuildInfo(deviceName, info);
114                     device.setRecovery(deviceConfig.getDeviceRecovery());
115                 } else {
116                     CLog.logAndDisplay(
117                             LogLevel.WARN,
118                             "No build found to test for device: %s",
119                             device.getSerialNumber());
120                     IBuildInfo notFoundStub = new BuildInfo();
121                     updateBuild(notFoundStub, config);
122                     context.addDeviceBuildInfo(currentDeviceName, notFoundStub);
123                     return false;
124                 }
125                 // TODO: remove build update when reporting is done on context
126                 updateBuild(info, config);
127                 info.setTestResourceBuild(config.isDeviceConfiguredFake(currentDeviceName));
128             }
129         } catch (BuildRetrievalError e) {
130             CLog.e(e);
131             if (currentDeviceName != null) {
132                 IBuildInfo errorBuild = e.getBuildInfo();
133                 updateBuild(errorBuild, config);
134                 context.addDeviceBuildInfo(currentDeviceName, errorBuild);
135                 updateInvocationContext(context, config);
136             }
137             throw e;
138         }
139         createSharedResources(context);
140         setAdbVersion(context);
141         return true;
142     }
143 
144     @Override
cleanUpBuilds(IInvocationContext context, IConfiguration config)145     public void cleanUpBuilds(IInvocationContext context, IConfiguration config) {
146         // Ensure build infos are always cleaned up at the end of invocation.
147         for (String cleanUpDevice : context.getDeviceConfigNames()) {
148             if (context.getBuildInfo(cleanUpDevice) != null) {
149                 try {
150                     config.getDeviceConfigByName(cleanUpDevice)
151                             .getBuildProvider()
152                             .cleanUp(context.getBuildInfo(cleanUpDevice));
153                 } catch (RuntimeException e) {
154                     // We catch an simply log exception in cleanUp to avoid missing any final
155                     // step of the invocation.
156                     CLog.e(e);
157                 }
158             }
159         }
160     }
161 
162     @Override
shardConfig( IConfiguration config, IInvocationContext context, IRescheduler rescheduler)163     public boolean shardConfig(
164             IConfiguration config, IInvocationContext context, IRescheduler rescheduler) {
165         return createShardHelper().shardConfig(config, context, rescheduler);
166     }
167 
168     /** Create an return the {@link IShardHelper} to be used. */
169     @VisibleForTesting
createShardHelper()170     protected IShardHelper createShardHelper() {
171         return GlobalConfiguration.getInstance().getShardingStrategy();
172     }
173 
174     @Override
doSetup( IInvocationContext context, IConfiguration config, final ITestInvocationListener listener)175     public void doSetup(
176             IInvocationContext context,
177             IConfiguration config,
178             final ITestInvocationListener listener)
179             throws TargetSetupError, BuildError, DeviceNotAvailableException {
180         long start = System.currentTimeMillis();
181         try {
182             // Before all the individual setup, make the multi-pre-target-preparer devices setup
183             runMultiTargetPreparers(
184                     config.getMultiPreTargetPreparers(),
185                     listener,
186                     context,
187                     "multi pre target preparer setup");
188 
189             // TODO: evaluate doing device setup in parallel
190             for (String deviceName : context.getDeviceConfigNames()) {
191                 ITestDevice device = context.getDevice(deviceName);
192                 CLog.d("Starting setup for device: '%s'", device.getSerialNumber());
193                 if (device instanceof ITestLoggerReceiver) {
194                     ((ITestLoggerReceiver) context.getDevice(deviceName)).setTestLogger(listener);
195                 }
196                 for (ITargetPreparer preparer :
197                         config.getDeviceConfigByName(deviceName).getTargetPreparers()) {
198                     // do not call the preparer if it was disabled
199                     if (preparer.isDisabled()) {
200                         CLog.d("%s has been disabled. skipping.", preparer);
201                         continue;
202                     }
203                     if (preparer instanceof ITestLoggerReceiver) {
204                         ((ITestLoggerReceiver) preparer).setTestLogger(listener);
205                     }
206                     CLog.d(
207                             "starting preparer '%s' on device: '%s'",
208                             preparer, device.getSerialNumber());
209                     preparer.setUp(device, context.getBuildInfo(deviceName));
210                     CLog.d(
211                             "done with preparer '%s' on device: '%s'",
212                             preparer, device.getSerialNumber());
213                 }
214                 CLog.d("Done with setup of device: '%s'", device.getSerialNumber());
215             }
216             // After all the individual setup, make the multi-devices setup
217             runMultiTargetPreparers(
218                     config.getMultiTargetPreparers(),
219                     listener,
220                     context,
221                     "multi target preparer setup");
222         } finally {
223             // Note: These metrics are handled in a try in case of a kernel reset or device issue.
224             // Setup timing metric. It does not include flashing time on boot tests.
225             long setupDuration = System.currentTimeMillis() - start;
226             context.addInvocationTimingMetric(IInvocationContext.TimingEvent.SETUP, setupDuration);
227             CLog.d("Setup duration: %s'", TimeUtil.formatElapsedTime(setupDuration));
228             // Upload the setup logcat after setup is complete.
229             for (String deviceName : context.getDeviceConfigNames()) {
230                 reportLogs(context.getDevice(deviceName), listener, Stage.SETUP);
231             }
232         }
233     }
234 
235     /** {@inheritDoc} */
236     @Override
runDevicePreInvocationSetup( IInvocationContext context, IConfiguration config, ITestLogger logger)237     public final void runDevicePreInvocationSetup(
238             IInvocationContext context, IConfiguration config, ITestLogger logger)
239             throws DeviceNotAvailableException, TargetSetupError {
240         for (String deviceName : context.getDeviceConfigNames()) {
241             ITestDevice device = context.getDevice(deviceName);
242 
243             CLog.d("Starting device pre invocation setup for : '%s'", device.getSerialNumber());
244             if (device instanceof ITestLoggerReceiver) {
245                 ((ITestLoggerReceiver) context.getDevice(deviceName)).setTestLogger(logger);
246             }
247             if (!config.getCommandOptions().shouldSkipPreDeviceSetup()) {
248                 device.preInvocationSetup(
249                         context.getBuildInfo(deviceName),
250                         context.getBuildInfos()
251                                 .stream()
252                                 .filter(buildInfo -> buildInfo.isTestResourceBuild())
253                                 .collect(Collectors.toList()));
254             }
255         }
256     }
257 
258     /** {@inheritDoc} */
259     @Override
runDevicePostInvocationTearDown( IInvocationContext context, IConfiguration config)260     public final void runDevicePostInvocationTearDown(
261             IInvocationContext context, IConfiguration config) {
262         // Extra tear down step for the device
263         for (String deviceName : context.getDeviceConfigNames()) {
264             ITestDevice device = context.getDevice(deviceName);
265             if (!config.getCommandOptions().shouldSkipPreDeviceSetup()) {
266                 device.postInvocationTearDown();
267             }
268         }
269     }
270 
271     /** Runs the {@link IMultiTargetPreparer} specified. */
runMultiTargetPreparers( List<IMultiTargetPreparer> multiPreparers, ITestLogger logger, IInvocationContext context, String description)272     private void runMultiTargetPreparers(
273             List<IMultiTargetPreparer> multiPreparers,
274             ITestLogger logger,
275             IInvocationContext context,
276             String description)
277             throws TargetSetupError, BuildError, DeviceNotAvailableException {
278         for (IMultiTargetPreparer multiPreparer : multiPreparers) {
279             // do not call the preparer if it was disabled
280             if (multiPreparer.isDisabled()) {
281                 CLog.d("%s has been disabled. skipping.", multiPreparer);
282                 continue;
283             }
284             if (multiPreparer instanceof ITestLoggerReceiver) {
285                 ((ITestLoggerReceiver) multiPreparer).setTestLogger(logger);
286             }
287             CLog.d("Starting %s '%s'", description, multiPreparer);
288             multiPreparer.setUp(context);
289             CLog.d("done with %s '%s'", description, multiPreparer);
290         }
291     }
292 
293     /** Runs the {@link IMultiTargetPreparer} specified tearDown. */
runMultiTargetPreparersTearDown( List<IMultiTargetPreparer> multiPreparers, IInvocationContext context, ITestLogger logger, Throwable throwable, String description)294     private Throwable runMultiTargetPreparersTearDown(
295             List<IMultiTargetPreparer> multiPreparers,
296             IInvocationContext context,
297             ITestLogger logger,
298             Throwable throwable,
299             String description)
300             throws Throwable {
301         ListIterator<IMultiTargetPreparer> iterator =
302                 multiPreparers.listIterator(multiPreparers.size());
303         Throwable deferredThrowable = null;
304 
305         while (iterator.hasPrevious()) {
306             IMultiTargetPreparer multipreparer = iterator.previous();
307             if (multipreparer.isDisabled() || multipreparer.isTearDownDisabled()) {
308                 CLog.d("%s has been disabled. skipping.", multipreparer);
309                 continue;
310             }
311             if (multipreparer instanceof ITestLoggerReceiver) {
312                 ((ITestLoggerReceiver) multipreparer).setTestLogger(logger);
313             }
314             CLog.d("Starting %s '%s'", description, multipreparer);
315             try {
316                 multipreparer.tearDown(context, throwable);
317             } catch (Throwable t) {
318                 // We catch it and rethrow later to allow each multi_targetprep to be attempted.
319                 // Only the first one will be thrown but all should be logged.
320                 CLog.e("Deferring throw for:");
321                 CLog.e(t);
322                 if (deferredThrowable == null) {
323                     deferredThrowable = t;
324                 }
325             }
326             CLog.d("Done with %s '%s'", description, multipreparer);
327         }
328 
329         return deferredThrowable;
330     }
331 
332     @Override
doTeardown( IInvocationContext context, IConfiguration config, ITestLogger logger, Throwable exception)333     public void doTeardown(
334             IInvocationContext context,
335             IConfiguration config,
336             ITestLogger logger,
337             Throwable exception)
338             throws Throwable {
339         Throwable deferredThrowable = null;
340 
341         List<IMultiTargetPreparer> multiPreparers = config.getMultiTargetPreparers();
342         deferredThrowable =
343                 runMultiTargetPreparersTearDown(
344                         multiPreparers,
345                         context,
346                         logger,
347                         exception,
348                         "multi target preparer teardown");
349 
350         // Clear wifi settings, to prevent wifi errors from interfering with teardown process.
351         for (String deviceName : context.getDeviceConfigNames()) {
352             ITestDevice device = context.getDevice(deviceName);
353             device.clearLastConnectedWifiNetwork();
354             List<ITargetPreparer> preparers =
355                     config.getDeviceConfigByName(deviceName).getTargetPreparers();
356             ListIterator<ITargetPreparer> itr = preparers.listIterator(preparers.size());
357             while (itr.hasPrevious()) {
358                 ITargetPreparer preparer = itr.previous();
359                 if (preparer instanceof ITargetCleaner) {
360                     ITargetCleaner cleaner = (ITargetCleaner) preparer;
361                     // do not call the cleaner if it was disabled
362                     if (cleaner.isDisabled() || cleaner.isTearDownDisabled()) {
363                         CLog.d("%s has been disabled. skipping.", cleaner);
364                         continue;
365                     }
366                     // If setup hit a targetSetupError, the setUp() and setTestLogger might not have
367                     // run, ensure we still have the logger.
368                     if (preparer instanceof ITestLoggerReceiver) {
369                         ((ITestLoggerReceiver) preparer).setTestLogger(logger);
370                     }
371                     try {
372                         CLog.d(
373                                 "starting tearDown '%s' on device: '%s'",
374                                 preparer, device.getSerialNumber());
375                         cleaner.tearDown(device, context.getBuildInfo(deviceName), exception);
376                         CLog.d(
377                                 "done with tearDown '%s' on device: '%s'",
378                                 preparer, device.getSerialNumber());
379                     } catch (Throwable e) {
380                         // We catch it and rethrow later to allow each targetprep to be attempted.
381                         // Only the first one will be thrown but all should be logged.
382                         CLog.e("Deferring throw for:");
383                         CLog.e(e);
384                         if (deferredThrowable == null) {
385                             deferredThrowable = e;
386                         }
387                     }
388                 }
389             }
390         }
391 
392         // Extra tear down step for the device
393         runDevicePostInvocationTearDown(context, config);
394 
395         // After all, run the multi_pre_target_preparer tearDown.
396         List<IMultiTargetPreparer> multiPrePreparers = config.getMultiPreTargetPreparers();
397         Throwable preTargetTearDownException =
398                 runMultiTargetPreparersTearDown(
399                         multiPrePreparers,
400                         context,
401                         logger,
402                         exception,
403                         "multi pre target preparer teardown");
404         if (deferredThrowable == null) {
405             deferredThrowable = preTargetTearDownException;
406         }
407 
408         if (deferredThrowable != null) {
409             throw deferredThrowable;
410         }
411     }
412 
413     @Override
doCleanUp(IInvocationContext context, IConfiguration config, Throwable exception)414     public void doCleanUp(IInvocationContext context, IConfiguration config, Throwable exception) {
415         for (String deviceName : context.getDeviceConfigNames()) {
416             List<ITargetPreparer> preparers =
417                     config.getDeviceConfigByName(deviceName).getTargetPreparers();
418             ListIterator<ITargetPreparer> itr = preparers.listIterator(preparers.size());
419             while (itr.hasPrevious()) {
420                 ITargetPreparer preparer = itr.previous();
421                 if (preparer instanceof IHostCleaner) {
422                     IHostCleaner cleaner = (IHostCleaner) preparer;
423                     if (preparer.isDisabled() || preparer.isTearDownDisabled()) {
424                         CLog.d("%s has been disabled. skipping.", cleaner);
425                         continue;
426                     }
427                     cleaner.cleanUp(context.getBuildInfo(deviceName), exception);
428                 }
429             }
430         }
431     }
432 
433     @Override
runTests( IInvocationContext context, IConfiguration config, ITestInvocationListener listener)434     public void runTests(
435             IInvocationContext context, IConfiguration config, ITestInvocationListener listener)
436             throws Throwable {
437         List<IRemoteTest> remainingTests = new ArrayList<>(config.getTests());
438         UnexecutedTestReporterThread reporterThread =
439                 new UnexecutedTestReporterThread(listener, remainingTests);
440         Runtime.getRuntime().addShutdownHook(reporterThread);
441         TestInvocation.printStageDelimiter(Stage.TEST, false);
442         try {
443             for (IRemoteTest test : config.getTests()) {
444                 // For compatibility of those receivers, they are assumed to be single device alloc.
445                 if (test instanceof IDeviceTest) {
446                     ((IDeviceTest) test).setDevice(context.getDevices().get(0));
447                 }
448                 if (test instanceof IBuildReceiver) {
449                     ((IBuildReceiver) test)
450                             .setBuild(context.getBuildInfo(context.getDevices().get(0)));
451                 }
452                 if (test instanceof ISystemStatusCheckerReceiver) {
453                     ((ISystemStatusCheckerReceiver) test)
454                             .setSystemStatusChecker(config.getSystemStatusCheckers());
455                 }
456 
457                 // TODO: consider adding receivers for only the list of ITestDevice and IBuildInfo.
458                 if (test instanceof IMultiDeviceTest) {
459                     ((IMultiDeviceTest) test).setDeviceInfos(context.getDeviceBuildMap());
460                 }
461                 if (test instanceof IInvocationContextReceiver) {
462                     ((IInvocationContextReceiver) test).setInvocationContext(context);
463                 }
464 
465                 updateAutoCollectors(config);
466 
467                 // We clone the collectors for each IRemoteTest to ensure no state conflicts.
468                 List<IMetricCollector> clonedCollectors = new ArrayList<>();
469                 // Add automated collectors
470                 for (AutoLogCollector auto : config.getCommandOptions().getAutoLogCollectors()) {
471                     clonedCollectors.add(auto.getInstanceForValue());
472                 }
473 
474                 // Add the collector from the configuration
475                 clonedCollectors.addAll(
476                         CollectorHelper.cloneCollectors(config.getMetricCollectors()));
477                 if (test instanceof IMetricCollectorReceiver) {
478                     ((IMetricCollectorReceiver) test).setMetricCollectors(clonedCollectors);
479                     // If test can receive collectors then let it handle the how to set them up
480                     test.run(listener);
481                 } else {
482                     // Wrap collectors in each other and collection will be sequential, do this in the
483                     // loop to ensure they are always initialized against the right context.
484                     ITestInvocationListener listenerWithCollectors = listener;
485                     for (IMetricCollector collector : clonedCollectors) {
486                         if (collector.isDisabled()) {
487                             CLog.d("%s has been disabled. Skipping.", collector);
488                         } else {
489                             listenerWithCollectors =
490                                     collector.init(context, listenerWithCollectors);
491                         }
492                     }
493                     test.run(listenerWithCollectors);
494                 }
495                 remainingTests.remove(test);
496             }
497         } finally {
498             TestInvocation.printStageDelimiter(Stage.TEST, true);
499             // TODO: Look if this can be improved to DeviceNotAvailableException too.
500             Runtime.getRuntime().removeShutdownHook(reporterThread);
501         }
502 
503     }
504 
505     @Override
resetBuildAndReschedule( Throwable exception, ITestInvocationListener listener, IConfiguration config, IInvocationContext context)506     public boolean resetBuildAndReschedule(
507             Throwable exception,
508             ITestInvocationListener listener,
509             IConfiguration config,
510             IInvocationContext context) {
511         if (!(exception instanceof BuildError) && !(exception.getCause() instanceof BuildError)) {
512             for (String deviceName : context.getDeviceConfigNames()) {
513                 config.getDeviceConfigByName(deviceName)
514                         .getBuildProvider()
515                         .buildNotTested(context.getBuildInfo(deviceName));
516             }
517             return true;
518         }
519         return false;
520     }
521 
522     @Override
reportLogs(ITestDevice device, ITestInvocationListener listener, Stage stage)523     public void reportLogs(ITestDevice device, ITestInvocationListener listener, Stage stage) {
524         if (device == null) {
525             return;
526         }
527         IDevice idevice = device.getIDevice();
528         // non stub device
529         if (!(idevice instanceof StubDevice)) {
530             try (InputStreamSource logcatSource = device.getLogcat()) {
531                 device.clearLogcat();
532                 String name =
533                         String.format(
534                                 "%s_%s",
535                                 TestInvocation.getDeviceLogName(stage), device.getSerialNumber());
536                 listener.testLog(name, LogDataType.LOGCAT, logcatSource);
537             }
538         }
539         // emulator logs
540         if (idevice != null && idevice.isEmulator()) {
541             try (InputStreamSource emulatorOutput = device.getEmulatorOutput()) {
542                 // TODO: Clear the emulator log
543                 String name = TestInvocation.getEmulatorLogName(stage);
544                 listener.testLog(name, LogDataType.TEXT, emulatorOutput);
545             }
546         }
547     }
548 
549     /**
550      * Update the {@link IInvocationContext} with additional info from the {@link IConfiguration}.
551      *
552      * @param context the {@link IInvocationContext}
553      * @param config the {@link IConfiguration}
554      */
updateInvocationContext(IInvocationContext context, IConfiguration config)555     void updateInvocationContext(IInvocationContext context, IConfiguration config) {
556         // TODO: Once reporting on context is done, only set context attributes
557         if (config.getCommandLine() != null) {
558             // TODO: obfuscate the password if any.
559             context.addInvocationAttribute(
560                     TestInvocation.COMMAND_ARGS_KEY, config.getCommandLine());
561         }
562         if (config.getCommandOptions().getShardCount() != null) {
563             context.addInvocationAttribute(
564                     "shard_count", config.getCommandOptions().getShardCount().toString());
565         }
566         if (config.getCommandOptions().getShardIndex() != null) {
567             context.addInvocationAttribute(
568                     "shard_index", config.getCommandOptions().getShardIndex().toString());
569         }
570         context.setTestTag(getTestTag(config));
571     }
572 
573     /** Helper to create the test tag from the configuration. */
getTestTag(IConfiguration config)574     private String getTestTag(IConfiguration config) {
575         String testTag = config.getCommandOptions().getTestTag();
576         if (config.getCommandOptions().getTestTagSuffix() != null) {
577             testTag =
578                     String.format("%s-%s", testTag, config.getCommandOptions().getTestTagSuffix());
579         }
580         return testTag;
581     }
582 
583     /** Handle setting the test tag on the build info. */
setTestTag(IBuildInfo info, IConfiguration config)584     protected void setTestTag(IBuildInfo info, IConfiguration config) {
585         // When CommandOption is set, it overrides any test-tag from build_providers
586         if (!"stub".equals(config.getCommandOptions().getTestTag())) {
587             info.setTestTag(getTestTag(config));
588         } else if (Strings.isNullOrEmpty(info.getTestTag())) {
589             // We ensure that that a default test-tag is always available.
590             info.setTestTag("stub");
591         } else {
592             CLog.w(
593                     "Using the test-tag from the build_provider. Consider updating your config to"
594                             + " have no alias/namespace in front of test-tag.");
595         }
596     }
597 
598     /**
599      * Update the {@link IBuildInfo} with additional info from the {@link IConfiguration}.
600      *
601      * @param info the {@link IBuildInfo}
602      * @param config the {@link IConfiguration}
603      */
updateBuild(IBuildInfo info, IConfiguration config)604     void updateBuild(IBuildInfo info, IConfiguration config) {
605         if (config.getCommandLine() != null) {
606             // TODO: obfuscate the password if any.
607             info.addBuildAttribute(TestInvocation.COMMAND_ARGS_KEY, config.getCommandLine());
608         }
609         if (config.getCommandOptions().getShardCount() != null) {
610             info.addBuildAttribute(
611                     "shard_count", config.getCommandOptions().getShardCount().toString());
612         }
613         if (config.getCommandOptions().getShardIndex() != null) {
614             info.addBuildAttribute(
615                     "shard_index", config.getCommandOptions().getShardIndex().toString());
616         }
617         setTestTag(info, config);
618 
619         if (info.getProperties().contains(BuildInfoProperties.DO_NOT_LINK_TESTS_DIR)) {
620             CLog.d("Skip linking external directory as FileProperty was set.");
621             return;
622         }
623         // Load environment tests dir.
624         if (info instanceof IDeviceBuildInfo) {
625             File testsDir = ((IDeviceBuildInfo) info).getTestsDir();
626             if (testsDir != null && testsDir.exists()) {
627                 handleLinkingExternalDirs(
628                         (IDeviceBuildInfo) info,
629                         testsDir,
630                         EnvVariable.ANDROID_TARGET_OUT_TESTCASES,
631                         BuildInfoFileKey.TARGET_LINKED_DIR.getFileKey());
632                 handleLinkingExternalDirs(
633                         (IDeviceBuildInfo) info,
634                         testsDir,
635                         EnvVariable.ANDROID_HOST_OUT_TESTCASES,
636                         BuildInfoFileKey.HOST_LINKED_DIR.getFileKey());
637             }
638         }
639     }
640 
handleLinkingExternalDirs( IDeviceBuildInfo info, File testsDir, EnvVariable var, String baseName)641     private void handleLinkingExternalDirs(
642             IDeviceBuildInfo info, File testsDir, EnvVariable var, String baseName) {
643         File externalDir = getExternalTestCasesDirs(var);
644         if (externalDir == null) {
645             String path = SystemUtil.ENV_VARIABLE_PATHS_IN_TESTS_DIR.get(var);
646             File varDir = FileUtil.getFileForPath(testsDir, path);
647             if (varDir.exists()) {
648                 // If we found a dir already in the tests dir we keep track of it
649                 info.setFile(
650                         baseName,
651                         varDir,
652                         /** version */
653                         "v1");
654             }
655             return;
656         }
657         try {
658             // Avoid conflict by creating a randomized name for the arriving symlink file.
659             File subDir = FileUtil.createTempDir(baseName, testsDir);
660             subDir.delete();
661             FileUtil.symlinkFile(externalDir, subDir);
662             // Tag the dir in the build info to be possibly cleaned.
663             info.setFile(
664                     baseName,
665                     subDir,
666                     /** version */
667                     "v1");
668             // Ensure we always delete the linking, no matter how the JVM exits.
669             subDir.deleteOnExit();
670         } catch (IOException e) {
671             CLog.e("Failed to load external test dir %s. Ignoring it.", externalDir);
672             CLog.e(e);
673         }
674     }
675 
676     /** Populate the shared resources directory for all non-resource build */
createSharedResources(IInvocationContext context)677     private void createSharedResources(IInvocationContext context) {
678         List<IBuildInfo> infos = context.getBuildInfos();
679         if (infos.size() <= 1) {
680             return;
681         }
682         try {
683             File resourcesDir = null;
684             for (IBuildInfo info : infos) {
685                 if (info.isTestResourceBuild()) {
686                     if (resourcesDir == null) {
687                         resourcesDir = FileUtil.createTempDir("invocation-resources-dir");
688                     }
689                     // Create a reception sub-folder for each build info resource to avoid mixing
690                     String name =
691                             String.format(
692                                     "%s_%s_%s",
693                                     info.getBuildBranch(),
694                                     info.getBuildId(),
695                                     info.getBuildFlavor());
696                     File buildDir = FileUtil.createTempDir(name, resourcesDir);
697                     for (BuildInfoFileKey key : BuildInfoKey.SHARED_KEY) {
698                         File f = info.getFile(key);
699                         if (f == null) {
700                             continue;
701                         }
702                         File subDir = new File(buildDir, f.getName());
703                         FileUtil.symlinkFile(f, subDir);
704                     }
705                 }
706             }
707             if (resourcesDir == null) {
708                 return;
709             }
710             // Only set the shared dir on real build if it exists.
711             CLog.d("Creating shared resources directory.");
712             for (IBuildInfo info : infos) {
713                 if (!info.isTestResourceBuild()) {
714                     info.setFile(BuildInfoFileKey.SHARED_RESOURCE_DIR, resourcesDir, "v1");
715                 }
716             }
717         } catch (IOException e) {
718             CLog.e("Failed to create the shared resources dir.");
719             CLog.e(e);
720         }
721     }
722 
setAdbVersion(IInvocationContext context)723     private void setAdbVersion(IInvocationContext context) {
724         String version = getAdbVersion();
725         if (version != null) {
726             context.addInvocationAttribute(ADB_VERSION_KEY, version);
727         }
728     }
729 
730     /** Convert the legacy *-on-failure options to the new auto-collect. */
updateAutoCollectors(IConfiguration config)731     private void updateAutoCollectors(IConfiguration config) {
732         if (config.getCommandOptions().captureScreenshotOnFailure()) {
733             config.getCommandOptions()
734                     .getAutoLogCollectors()
735                     .add(AutoLogCollector.SCREENSHOT_ON_FAILURE);
736         }
737         if (config.getCommandOptions().captureLogcatOnFailure()) {
738             config.getCommandOptions()
739                     .getAutoLogCollectors()
740                     .add(AutoLogCollector.LOGCAT_ON_FAILURE);
741         }
742     }
743 
744     /** Returns the external directory coming from the environment. */
745     @VisibleForTesting
getExternalTestCasesDirs(EnvVariable envVar)746     File getExternalTestCasesDirs(EnvVariable envVar) {
747         return SystemUtil.getExternalTestCasesDir(envVar);
748     }
749 
750     /** Returns the adb version in use for the invocation. */
getAdbVersion()751     protected String getAdbVersion() {
752         return GlobalConfiguration.getDeviceManagerInstance().getAdbVersion();
753     }
754 }
755