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