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