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