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