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.compatibility.common.tradefed.result.suite; 17 18 import com.android.annotations.VisibleForTesting; 19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 20 import com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider; 21 import com.android.compatibility.common.tradefed.targetprep.BuildFingerPrintPreparer; 22 import com.android.compatibility.common.util.ResultHandler; 23 import com.android.ddmlib.Log.LogLevel; 24 import com.android.tradefed.build.BuildRetrievalError; 25 import com.android.tradefed.build.IBuildInfo; 26 import com.android.tradefed.build.IBuildProvider; 27 import com.android.tradefed.config.IConfiguration; 28 import com.android.tradefed.config.Option; 29 import com.android.tradefed.invoker.IInvocationContext; 30 import com.android.tradefed.invoker.InvocationContext; 31 import com.android.tradefed.invoker.TestInvocation; 32 import com.android.tradefed.invoker.proto.InvocationContext.Context; 33 import com.android.tradefed.log.LogUtil.CLog; 34 import com.android.tradefed.result.CollectingTestListener; 35 import com.android.tradefed.result.ITestInvocationListener; 36 import com.android.tradefed.result.proto.ProtoResultParser; 37 import com.android.tradefed.result.proto.TestRecordProto.TestRecord; 38 import com.android.tradefed.result.suite.SuiteResultHolder; 39 import com.android.tradefed.result.suite.XmlSuiteResultFormatter.RunHistory; 40 import com.android.tradefed.targetprep.ITargetPreparer; 41 import com.android.tradefed.testtype.suite.retry.ITestSuiteResultLoader; 42 import com.android.tradefed.util.TestRecordInterpreter; 43 import com.android.tradefed.util.proto.TestRecordProtoUtil; 44 45 import com.google.common.base.Strings; 46 import com.google.gson.Gson; 47 import com.google.protobuf.InvalidProtocolBufferException; 48 49 import java.io.File; 50 import java.io.IOException; 51 import java.util.ArrayList; 52 import java.util.Collection; 53 import java.util.Collections; 54 import java.util.List; 55 56 /** 57 * Implementation of {@link ITestSuiteResultLoader} to reload CTS previous results. 58 */ 59 public final class PreviousResultLoader implements ITestSuiteResultLoader { 60 61 /** Usually associated with ro.build.fingerprint. */ 62 public static final String BUILD_FINGERPRINT = "build_fingerprint"; 63 /** Usally associated with ro.vendor.build.fingerprint. */ 64 public static final String BUILD_VENDOR_FINGERPRINT = "build_vendor_fingerprint"; 65 /** 66 * Some suites have a business need to alter the original real device fingerprint value, in this 67 * case we expect an "unaltered" version to be available to still do the original check. 68 */ 69 public static final String BUILD_FINGERPRINT_UNALTERED = "build_fingerprint_unaltered"; 70 /** Used to get run history from the invocation context of last run. */ 71 public static final String RUN_HISTORY_KEY = "run_history"; 72 73 private static final String COMMAND_LINE_ARGS = "command_line_args"; 74 75 public static final String RETRY_OPTION = "retry"; 76 77 @Option( 78 name = RETRY_OPTION, 79 shortName = 'r', 80 description = "retry a previous session's failed and not executed tests.", 81 mandatory = true) 82 private Integer mRetrySessionId = null; 83 84 @Option( 85 name = "fingerprint-property", 86 description = "The property name to check for the fingerprint." 87 ) 88 private String mFingerprintProperty = "ro.build.fingerprint"; 89 90 private TestRecord mTestRecord; 91 private String mProtoPath = null; 92 private IInvocationContext mPreviousContext; 93 private String mExpectedFingerprint; 94 private String mExpectedVendorFingerprint; 95 private String mUnalteredFingerprint; 96 97 private File mResultDir; 98 99 private IBuildProvider mProvider; 100 101 /** 102 * The run history of last run (last run excluded) will be first parsed out and stored here, the 103 * information of last run is added second. Then this object is serialized and added to the 104 * configuration of the current test run. 105 */ 106 private Collection<RunHistory> mRunHistories; 107 108 @Override init()109 public void init() { 110 IBuildInfo info = null; 111 try { 112 info = getProvider().getBuild(); 113 } catch (BuildRetrievalError e) { 114 throw new RuntimeException(e); 115 } 116 CompatibilityBuildHelper helperBuild = new CompatibilityBuildHelper(info); 117 mResultDir = null; 118 try { 119 CLog.logAndDisplay(LogLevel.DEBUG, "Start loading the record protobuf."); 120 mResultDir = 121 ResultHandler.getResultDirectory(helperBuild.getResultsDir(), mRetrySessionId); 122 File protoDir = new File(mResultDir, CompatibilityProtoResultReporter.PROTO_DIR); 123 // Check whether we have multiple protos or one 124 if (new File(protoDir, CompatibilityProtoResultReporter.PROTO_FILE_NAME).exists()) { 125 mTestRecord = 126 TestRecordProtoUtil.readFromFile( 127 new File( 128 protoDir, 129 CompatibilityProtoResultReporter.PROTO_FILE_NAME)); 130 } else if (new File(protoDir, CompatibilityProtoResultReporter.PROTO_FILE_NAME + "0") 131 .exists()) { 132 // Use proto0 to get the basic information since it should be the invocation proto. 133 mTestRecord = 134 TestRecordProtoUtil.readFromFile( 135 new File( 136 protoDir, 137 CompatibilityProtoResultReporter.PROTO_FILE_NAME + "0")); 138 mProtoPath = 139 new File(protoDir, CompatibilityProtoResultReporter.PROTO_FILE_NAME) 140 .getAbsolutePath(); 141 } else { 142 throw new RuntimeException("Could not find any test-record.pb to load."); 143 } 144 145 CLog.logAndDisplay(LogLevel.DEBUG, "Done loading the record protobuf."); 146 } catch (IOException e) { 147 throw new RuntimeException(e); 148 } 149 150 Context contextProto = null; 151 try { 152 contextProto = mTestRecord.getDescription().unpack(Context.class); 153 } catch (InvalidProtocolBufferException e) { 154 throw new RuntimeException(e); 155 } 156 mPreviousContext = InvocationContext.fromProto(contextProto); 157 158 mRunHistories = new ArrayList<>(); 159 String runHistoryJSON = 160 mPreviousContext.getAttributes().getUniqueMap().get(RUN_HISTORY_KEY); 161 if (runHistoryJSON != null) { 162 Gson gson = new Gson(); 163 RunHistory[] runHistories = gson.fromJson(runHistoryJSON, RunHistory[].class); 164 Collections.addAll(mRunHistories, runHistories); 165 } 166 167 // Validate the fingerprint 168 // TODO: Use fingerprint argument from TestRecord but we have to deal with suite namespace 169 // for example: cts:build_fingerprint instead of just build_fingerprint. 170 // And update run history. 171 try { 172 CLog.logAndDisplay(LogLevel.DEBUG, "Start parsing previous test_results.xml"); 173 CertificationResultXml xmlParser = new CertificationResultXml(); 174 SuiteResultHolder holder = xmlParser.parseResults(mResultDir, true); 175 CLog.logAndDisplay(LogLevel.DEBUG, "Done parsing previous test_results.xml"); 176 mExpectedFingerprint = holder.context.getAttributes() 177 .getUniqueMap().get(BUILD_FINGERPRINT); 178 if (mExpectedFingerprint == null) { 179 throw new IllegalArgumentException( 180 String.format( 181 "Could not find the %s field in the loaded result.", 182 BUILD_FINGERPRINT)); 183 } 184 /** If available in the report, collect the vendor fingerprint too. */ 185 mExpectedVendorFingerprint = 186 holder.context.getAttributes().getUniqueMap().get(BUILD_VENDOR_FINGERPRINT); 187 if (mExpectedVendorFingerprint == null) { 188 throw new IllegalArgumentException( 189 String.format( 190 "Could not find the %s field in the loaded result.", 191 BUILD_VENDOR_FINGERPRINT)); 192 } 193 // Some cases will have an unaltered fingerprint 194 mUnalteredFingerprint = 195 holder.context.getAttributes().getUniqueMap().get(BUILD_FINGERPRINT_UNALTERED); 196 197 // Add the information of last test run to a run history list. 198 RunHistory newRun = new RunHistory(); 199 newRun.startTime = holder.startTime; 200 newRun.endTime = holder.endTime; 201 newRun.passedTests = holder.passedTests; 202 newRun.failedTests = holder.failedTests; 203 newRun.commandLineArgs = 204 Strings.nullToEmpty( 205 holder.context.getAttributes().getUniqueMap().get(COMMAND_LINE_ARGS)); 206 newRun.hostName = holder.hostName; 207 mRunHistories.add(newRun); 208 } catch (IOException e) { 209 throw new RuntimeException(e); 210 } 211 } 212 213 @Override getCommandLine()214 public String getCommandLine() { 215 List<String> command = mPreviousContext.getAttributes().get( 216 TestInvocation.COMMAND_ARGS_KEY); 217 if (command == null) { 218 throw new RuntimeException("Couldn't find the command_line_args."); 219 } 220 return command.get(0); 221 } 222 223 @Override loadPreviousResults()224 public CollectingTestListener loadPreviousResults() { 225 if (mProtoPath != null) { 226 int index = 0; 227 CollectingTestListener results = new CollectingTestListener(); 228 ProtoResultParser parser = new ProtoResultParser(results, null, true); 229 while (new File(mProtoPath + index).exists()) { 230 try { 231 parser.processFileProto(new File(mProtoPath + index)); 232 } catch (IOException e) { 233 throw new RuntimeException(e); 234 } 235 index++; 236 } 237 return results; 238 } 239 return TestRecordInterpreter.interpreteRecord(mTestRecord); 240 } 241 242 @Override cleanUp()243 public final void cleanUp() { 244 if (mTestRecord != null) { 245 mTestRecord = null; 246 } 247 } 248 249 @Override customizeConfiguration(IConfiguration config)250 public final void customizeConfiguration(IConfiguration config) { 251 // This is specific to Compatibility checking and does not work for multi-device. 252 List<ITargetPreparer> preparers = config.getTargetPreparers(); 253 List<ITargetPreparer> newList = new ArrayList<>(); 254 // Add the fingerprint checker last to support preparers that need to flash or launch 255 // the device 256 BuildFingerPrintPreparer fingerprintChecker = new BuildFingerPrintPreparer(); 257 fingerprintChecker.setExpectedFingerprint(mExpectedFingerprint); 258 fingerprintChecker.setExpectedVendorFingerprint(mExpectedVendorFingerprint); 259 fingerprintChecker.setFingerprintProperty(mFingerprintProperty); 260 if (!Strings.isNullOrEmpty(mUnalteredFingerprint)) { 261 fingerprintChecker.setUnalteredFingerprint(mUnalteredFingerprint); 262 } 263 newList.addAll(preparers); 264 newList.add(fingerprintChecker); 265 config.setTargetPreparers(newList); 266 267 // Add the file copier last to copy from previous sesssion 268 List<ITestInvocationListener> listeners = config.getTestInvocationListeners(); 269 PreviousSessionFileCopier copier = new PreviousSessionFileCopier(); 270 copier.setPreviousSessionDir(mResultDir); 271 listeners.add(copier); 272 273 // Add run history to the configuration so it will be augmented in the next test run. 274 Gson gson = new Gson(); 275 config.getCommandOptions() 276 .getInvocationData() 277 .put(RUN_HISTORY_KEY, gson.toJson(mRunHistories)); 278 } 279 280 @VisibleForTesting setProvider(IBuildProvider provider)281 protected void setProvider(IBuildProvider provider) { 282 mProvider = provider; 283 } 284 getProvider()285 private IBuildProvider getProvider() { 286 if (mProvider == null) { 287 mProvider = new CompatibilityBuildProvider(); 288 } 289 return mProvider; 290 } 291 } 292