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