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