1 /*
2  * Copyright (C) 2022 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.compatibility.common.tradefed.build.CompatibilityBuildHelper;
19 import com.android.compatibility.common.util.ResultUploader;
20 import com.android.tradefed.config.IConfiguration;
21 import com.android.tradefed.config.IConfigurationReceiver;
22 import com.android.tradefed.config.Option;
23 import com.android.tradefed.config.OptionClass;
24 import com.android.tradefed.invoker.IInvocationContext;
25 import com.android.tradefed.log.LogUtil.CLog;
26 import com.android.tradefed.result.FileInputStreamSource;
27 import com.android.tradefed.result.ILogSaver;
28 import com.android.tradefed.result.ILogSaverListener;
29 import com.android.tradefed.result.ITestInvocationListener;
30 import com.android.tradefed.result.ITestSummaryListener;
31 import com.android.tradefed.result.LogDataType;
32 import com.android.tradefed.result.LogFile;
33 import com.android.tradefed.result.TestSummary;
34 import com.android.tradefed.util.FileUtil;
35 import com.android.tradefed.util.IDisableable;
36 import com.android.tradefed.util.StreamUtil;
37 import com.android.tradefed.util.ZipUtil;
38 
39 import java.io.File;
40 import java.io.FileInputStream;
41 import java.io.FileNotFoundException;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.OutputStream;
46 import java.util.List;
47 
48 import javax.xml.transform.Transformer;
49 import javax.xml.transform.TransformerException;
50 import javax.xml.transform.TransformerFactory;
51 import javax.xml.transform.stream.StreamResult;
52 import javax.xml.transform.stream.StreamSource;
53 
54 /** Package all the results into the zip and allow to upload it. */
55 @OptionClass(alias = "result-reporter")
56 public class CertificationReportCreator
57         implements ILogSaverListener, ITestSummaryListener, IConfigurationReceiver, IDisableable {
58 
59     public static final String HTLM_REPORT_NAME = "test_result.html";
60     public static final String REPORT_XSL_FILE_NAME = "compatibility_result.xsl";
61     public static final String FAILURE_REPORT_NAME = "test_result_failures_suite.html";
62     public static final String FAILURE_XSL_FILE_NAME = "compatibility_failures.xsl";
63 
64     public static final String INCLUDE_HTML_IN_ZIP = "html-in-zip";
65 
66     @Option(name = "disable", description = "Whether or not to disable this reporter.")
67     private boolean mDisable = false;
68 
69     @Option(
70             name = INCLUDE_HTML_IN_ZIP,
71             description = "Whether failure summary report is included in the zip fie.")
72     private boolean mIncludeHtml = false;
73 
74     @Option(name = "result-server", description = "Server to publish test results.")
75     private String mResultServer;
76 
77     @Option(
78             name = "disable-result-posting",
79             description = "Disable result posting into report server.")
80     private boolean mDisableResultPosting = false;
81 
82     @Option(name = "use-log-saver", description = "Also saves generated result with log saver")
83     private boolean mUseLogSaver = false;
84 
85     /** Invocation level Log saver to receive when files are logged */
86     private ILogSaver mLogSaver;
87 
88     private IConfiguration mConfiguration;
89 
90     private CompatibilityBuildHelper mBuildHelper;
91 
92     private String mReferenceUrl;
93 
94     private File mReportFile;
95 
96     /** {@inheritDoc} */
97     @Override
setLogSaver(ILogSaver saver)98     public void setLogSaver(ILogSaver saver) {
99         mLogSaver = saver;
100     }
101 
102     /** {@inheritDoc} */
103     @Override
putSummary(List<TestSummary> summaries)104     public void putSummary(List<TestSummary> summaries) {
105         for (TestSummary summary : summaries) {
106             if (mReferenceUrl == null && summary.getSummary().getString() != null) {
107                 mReferenceUrl = summary.getSummary().getString();
108             }
109         }
110     }
111 
112     @Override
setConfiguration(IConfiguration configuration)113     public void setConfiguration(IConfiguration configuration) {
114         mConfiguration = configuration;
115     }
116 
getConfiguration()117     private IConfiguration getConfiguration() {
118         return mConfiguration;
119     }
120 
121     @Override
invocationStarted(IInvocationContext context)122     public void invocationStarted(IInvocationContext context) {
123         if (mBuildHelper == null) {
124             mBuildHelper = new CompatibilityBuildHelper(context.getBuildInfos().get(0));
125         }
126     }
127 
setReportFile(File reportFile)128     public void setReportFile(File reportFile) {
129         mReportFile = reportFile;
130     }
131 
132     @Override
invocationEnded(long elapsedTime)133     public void invocationEnded(long elapsedTime) {
134         if (mReportFile == null) {
135             CLog.w("Did not receive the report file to be packaged");
136             return;
137         }
138         File resultDir;
139         try {
140             resultDir = mBuildHelper.getResultDir();
141         } catch (FileNotFoundException e) {
142             throw new RuntimeException(e);
143         }
144         File report = null;
145         File failureReport = null;
146         if (mIncludeHtml) {
147             // Create the html reports before the zip file.
148             report = createReport(mReportFile);
149             failureReport = createFailureReport(mReportFile);
150         }
151         File zippedResults = zipResults(resultDir);
152         if (!mIncludeHtml) {
153             // Create html reports after zip file so extra data is not uploaded
154             report = createReport(mReportFile);
155             failureReport = createFailureReport(mReportFile);
156         }
157         if (report != null) {
158             CLog.i("Viewable report: %s", report.getAbsolutePath());
159         }
160         try {
161             if (failureReport.exists()) {
162                 CLog.i("Test Result: %s", failureReport.getCanonicalPath());
163             } else {
164                 CLog.i("Test Result: %s", mReportFile.getCanonicalPath());
165             }
166 
167             saveLog(mReportFile, zippedResults);
168         } catch (IOException e) {
169             CLog.e("Error when handling the post processing of results file:");
170             CLog.e(e);
171         }
172 
173         uploadResult(mReportFile);
174     }
175 
176     /**
177      * Zip the contents of the given results directory. CTS specific.
178      *
179      * @param resultsDir
180      */
zipResults(File resultsDir)181     private static File zipResults(File resultsDir) {
182         File zipResultFile = null;
183         try {
184             // create a file in parent directory, with same name as resultsDir
185             zipResultFile =
186                     new File(resultsDir.getParent(), String.format("%s.zip", resultsDir.getName()));
187             ZipUtil.createZip(resultsDir, zipResultFile);
188         } catch (IOException e) {
189             CLog.w("Failed to create zip for %s", resultsDir.getName());
190         }
191         return zipResultFile;
192     }
193 
194     /** When enabled, upload the result to a server. CTS specific. */
uploadResult(File resultFile)195     private void uploadResult(File resultFile) {
196         if (mResultServer != null && !mResultServer.trim().isEmpty() && !mDisableResultPosting) {
197             ResultUploader uploader =
198                     new ResultUploader(mResultServer, mBuildHelper.getSuiteName());
199             try {
200                 CLog.d("Result Server: %d", uploader.uploadResult(resultFile, mReferenceUrl));
201             } catch (IOException ioe) {
202                 CLog.e("IOException while uploading result.");
203                 CLog.e(ioe);
204             }
205         }
206     }
207 
208     /** When enabled, save log data using log saver */
saveLog(File resultFile, File zippedResults)209     private void saveLog(File resultFile, File zippedResults) throws IOException {
210         if (!mUseLogSaver) {
211             return;
212         }
213 
214         FileInputStream fis = null;
215         LogFile logFile = null;
216         try {
217             fis = new FileInputStream(resultFile);
218             logFile = mLogSaver.saveLogData("log-result", LogDataType.XML, fis);
219             CLog.d("Result XML URL: %s", logFile.getUrl());
220             logReportFiles(getConfiguration(), resultFile, resultFile.getName(), LogDataType.XML);
221         } catch (IOException ioe) {
222             CLog.e("error saving XML with log saver");
223             CLog.e(ioe);
224         } finally {
225             StreamUtil.close(fis);
226         }
227         // Save the full results folder.
228         if (zippedResults != null) {
229             FileInputStream zipResultStream = null;
230             try {
231                 zipResultStream = new FileInputStream(zippedResults);
232                 logFile = mLogSaver.saveLogData("results", LogDataType.ZIP, zipResultStream);
233                 CLog.d("Result zip URL: %s", logFile.getUrl());
234                 logReportFiles(getConfiguration(), zippedResults, "results", LogDataType.ZIP);
235             } finally {
236                 StreamUtil.close(zipResultStream);
237             }
238         }
239     }
240 
241     /** Generate html report. */
createReport(File inputXml)242     private File createReport(File inputXml) {
243         File report = new File(inputXml.getParentFile(), HTLM_REPORT_NAME);
244         try (InputStream xslStream =
245                         new FileInputStream(
246                                 new File(inputXml.getParentFile(), REPORT_XSL_FILE_NAME));
247                 OutputStream outputStream = new FileOutputStream(report)) {
248             Transformer transformer =
249                     TransformerFactory.newInstance().newTransformer(new StreamSource(xslStream));
250             transformer.setParameter("reportDir", inputXml.getParentFile().toString());
251             transformer.setParameter("reportName", HTLM_REPORT_NAME);
252             transformer.transform(new StreamSource(inputXml), new StreamResult(outputStream));
253         } catch (IOException | TransformerException ignored) {
254             CLog.e(ignored);
255             FileUtil.deleteFile(report);
256             return null;
257         }
258         return report;
259     }
260 
261     /** Generate html report listing an failed tests. CTS specific. */
createFailureReport(File inputXml)262     private File createFailureReport(File inputXml) {
263         File failureReport = new File(inputXml.getParentFile(), FAILURE_REPORT_NAME);
264         try (InputStream xslStream =
265                         CertificationReportCreator.class.getResourceAsStream(
266                                 String.format("/report/%s", FAILURE_XSL_FILE_NAME));
267                 OutputStream outputStream = new FileOutputStream(failureReport)) {
268 
269             Transformer transformer =
270                     TransformerFactory.newInstance().newTransformer(new StreamSource(xslStream));
271             transformer.transform(new StreamSource(inputXml), new StreamResult(outputStream));
272         } catch (IOException | TransformerException ignored) {
273             CLog.e(ignored);
274         }
275         return failureReport;
276     }
277 
278     /** Re-log a result file to all reporters so they are aware of it. */
logReportFiles( IConfiguration configuration, File resultFile, String dataName, LogDataType type)279     private void logReportFiles(
280             IConfiguration configuration, File resultFile, String dataName, LogDataType type) {
281         if (configuration == null) {
282             return;
283         }
284         ILogSaver saver = configuration.getLogSaver();
285         List<ITestInvocationListener> listeners = configuration.getTestInvocationListeners();
286         try (FileInputStreamSource source = new FileInputStreamSource(resultFile)) {
287             LogFile loggedFile = null;
288             try (InputStream stream = source.createInputStream()) {
289                 loggedFile = saver.saveLogData(dataName, type, stream);
290             } catch (IOException e) {
291                 CLog.e(e);
292             }
293             for (ITestInvocationListener listener : listeners) {
294                 if (listener.equals(this)) {
295                     // Avoid logging against itself
296                     continue;
297                 }
298                 listener.testLog(dataName, type, source);
299                 if (loggedFile != null) {
300                     if (listener instanceof ILogSaverListener) {
301                         ((ILogSaverListener) listener).logAssociation(dataName, loggedFile);
302                     }
303                 }
304             }
305         }
306     }
307 
308     @Override
isDisabled()309     public boolean isDisabled() {
310         return mDisable;
311     }
312 }
313