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.testtype.suite.retry;
17 
18 import com.android.annotations.VisibleForTesting;
19 import com.android.ddmlib.Log.LogLevel;
20 import com.android.tradefed.config.IConfiguration;
21 import com.android.tradefed.config.IConfigurationReceiver;
22 import com.android.tradefed.device.DeviceNotAvailableException;
23 import com.android.tradefed.device.ITestDevice;
24 import com.android.tradefed.device.StubDevice;
25 import com.android.tradefed.invoker.IInvocationContext;
26 import com.android.tradefed.invoker.TestInformation;
27 import com.android.tradefed.log.LogUtil.CLog;
28 import com.android.tradefed.result.ILogSaverListener;
29 import com.android.tradefed.result.ITestInvocationListener;
30 import com.android.tradefed.result.LogFile;
31 import com.android.tradefed.result.TestDescription;
32 import com.android.tradefed.result.TestResult;
33 import com.android.tradefed.result.TestRunResult;
34 import com.android.tradefed.testtype.IRemoteTest;
35 import com.android.tradefed.util.TimeUtil;
36 
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.LinkedHashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Map.Entry;
43 
44 /** Special runner that replays the results given to it. */
45 public final class ResultsPlayer implements IRemoteTest, IConfigurationReceiver {
46 
47     private class ReplayModuleHolder {
48         public IInvocationContext mModuleContext;
49         public List<Entry<TestDescription, TestResult>> mResults = new ArrayList<>();
50     }
51 
52     private Map<TestRunResult, ReplayModuleHolder> mModuleResult;
53     private IConfiguration mConfiguration;
54     private boolean mCompleted;
55 
56     /** Ctor. */
ResultsPlayer()57     public ResultsPlayer() {
58         mModuleResult = new LinkedHashMap<>();
59     }
60 
61     @VisibleForTesting
ResultsPlayer(boolean completed)62     public ResultsPlayer(boolean completed) {
63         mCompleted = completed;
64     }
65 
66     @Override
run(TestInformation testInfo, ITestInvocationListener listener)67     public void run(TestInformation testInfo, ITestInvocationListener listener)
68             throws DeviceNotAvailableException {
69         // Very first thing of the retry is to check whether all devices are available, this avoids
70         // use wasting time replaying result for an invocation that will fail right after during
71         // the re-run.
72         for (ITestDevice device : testInfo.getContext().getDevices()) {
73             if (device.getIDevice() instanceof StubDevice) {
74                 continue;
75             }
76             device.waitForDeviceAvailable();
77         }
78 
79         long startReplay = System.currentTimeMillis();
80         CLog.logAndDisplay(
81                 LogLevel.DEBUG,
82                 "Start replaying the previous results. Please wait this can take a few minutes.");
83         // Change the logging level to avoid too much logs from the replay.
84         LogLevel originalLevel = mConfiguration.getLogOutput().getLogLevel();
85         mConfiguration.getLogOutput().setLogLevel(LogLevel.WARN);
86 
87         for (TestRunResult module : mModuleResult.keySet()) {
88             ReplayModuleHolder holder = mModuleResult.get(module);
89 
90             IInvocationContext moduleContext = holder.mModuleContext;
91             if (moduleContext != null) {
92                 for (String deviceName : testInfo.getContext().getDeviceConfigNames()) {
93                     moduleContext.addAllocatedDevice(
94                             deviceName, testInfo.getContext().getDevice(deviceName));
95                     moduleContext.addDeviceBuildInfo(
96                             deviceName, testInfo.getContext().getBuildInfo(deviceName));
97                 }
98                 listener.testModuleStarted(moduleContext);
99             }
100 
101             // Replay full or partial results
102             Collection<Entry<TestDescription, TestResult>> testSet = holder.mResults;
103             if (testSet.isEmpty()) {
104                 testSet = module.getTestResults().entrySet();
105             }
106 
107             forwardTestResults(module, testSet, listener);
108 
109             if (moduleContext != null) {
110                 listener.testModuleEnded();
111             }
112 
113             // Clean up the memory: IRemoteTest object are kept in memory until the command finish
114             // So we need to clean up the entries when we are done with them to free up the
115             // memory early
116             holder.mResults.clear();
117         }
118         // Restore the original log level to continue execution with the requested log level.
119         mConfiguration.getLogOutput().setLogLevel(originalLevel);
120         CLog.logAndDisplay(
121                 LogLevel.DEBUG,
122                 "Done replaying results in %s",
123                 TimeUtil.formatElapsedTime(System.currentTimeMillis() - startReplay));
124         mModuleResult.clear();
125         mCompleted = true;
126     }
127 
128     /**
129      * Register a module to be replayed.
130      *
131      * @param moduleContext The Context of the module. Or null if it's a simple test run.
132      * @param module The results of the test run or module.
133      * @param testResult The particular test and its result to replay. Can be null if the full
134      *     module should be replayed.
135      */
addToReplay( IInvocationContext moduleContext, TestRunResult module, Entry<TestDescription, TestResult> testResult)136     void addToReplay(
137             IInvocationContext moduleContext,
138             TestRunResult module,
139             Entry<TestDescription, TestResult> testResult) {
140         ReplayModuleHolder holder = mModuleResult.get(module);
141         if (holder == null) {
142             holder = new ReplayModuleHolder();
143             holder.mModuleContext = moduleContext;
144             mModuleResult.put(module, holder);
145         }
146         if (testResult != null) {
147             holder.mResults.add(testResult);
148         }
149     }
150 
151     /** {@inheritDoc} */
152     @Override
setConfiguration(IConfiguration configuration)153     public void setConfiguration(IConfiguration configuration) {
154         mConfiguration = configuration;
155     }
156 
157     /** Returns whether or not the ResultsReplayer is done replaying the results. */
completed()158     public boolean completed() {
159         return mCompleted;
160     }
161 
forwardTestResults( TestRunResult module, Collection<Entry<TestDescription, TestResult>> testSet, ITestInvocationListener listener)162     private void forwardTestResults(
163             TestRunResult module,
164             Collection<Entry<TestDescription, TestResult>> testSet,
165             ITestInvocationListener listener) {
166         listener.testRunStarted(module.getName(), testSet.size());
167         for (Map.Entry<TestDescription, TestResult> testEntry : testSet) {
168             listener.testStarted(testEntry.getKey(), testEntry.getValue().getStartTime());
169             switch (testEntry.getValue().getStatus()) {
170                 case FAILURE:
171                     listener.testFailed(testEntry.getKey(), testEntry.getValue().getStackTrace());
172                     break;
173                 case ASSUMPTION_FAILURE:
174                     listener.testAssumptionFailure(
175                             testEntry.getKey(), testEntry.getValue().getStackTrace());
176                     break;
177                 case IGNORED:
178                     listener.testIgnored(testEntry.getKey());
179                     break;
180                 case INCOMPLETE:
181                     listener.testFailed(
182                             testEntry.getKey(), "Test did not complete due to exception.");
183                     break;
184                 default:
185                     break;
186             }
187             // Provide a strong association of the test to its logs.
188             for (Entry<String, LogFile> logFile :
189                     testEntry.getValue().getLoggedFiles().entrySet()) {
190                 if (listener instanceof ILogSaverListener) {
191                     ((ILogSaverListener) listener)
192                             .logAssociation(logFile.getKey(), logFile.getValue());
193                 }
194             }
195             listener.testEnded(
196                     testEntry.getKey(),
197                     testEntry.getValue().getEndTime(),
198                     testEntry.getValue().getProtoMetrics());
199         }
200         if (module.isRunFailure()) {
201             listener.testRunFailed(module.getRunFailureMessage());
202         }
203         listener.testRunEnded(module.getElapsedTime(), module.getRunProtoMetrics());
204     }
205 }
206