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.util.DeviceInfo;
21 import com.android.compatibility.common.util.ResultHandler;
22 import com.android.compatibility.common.util.ResultUploader;
23 import com.android.tradefed.build.IBuildInfo;
24 import com.android.tradefed.cluster.SubprocessConfigBuilder;
25 import com.android.tradefed.config.IConfiguration;
26 import com.android.tradefed.config.Option;
27 import com.android.tradefed.config.OptionClass;
28 import com.android.tradefed.invoker.IInvocationContext;
29 import com.android.tradefed.log.LogUtil.CLog;
30 import com.android.tradefed.result.FileInputStreamSource;
31 import com.android.tradefed.result.ILogSaver;
32 import com.android.tradefed.result.ILogSaverListener;
33 import com.android.tradefed.result.ITestInvocationListener;
34 import com.android.tradefed.result.ITestSummaryListener;
35 import com.android.tradefed.result.InputStreamSource;
36 import com.android.tradefed.result.LogDataType;
37 import com.android.tradefed.result.LogFile;
38 import com.android.tradefed.result.LogFileSaver;
39 import com.android.tradefed.result.SnapshotInputStreamSource;
40 import com.android.tradefed.result.TestRunResult;
41 import com.android.tradefed.result.TestSummary;
42 import com.android.tradefed.result.suite.IFormatterGenerator;
43 import com.android.tradefed.result.suite.SuiteResultReporter;
44 import com.android.tradefed.result.suite.XmlFormattedGeneratorReporter;
45 import com.android.tradefed.util.FileUtil;
46 import com.android.tradefed.util.StreamUtil;
47 import com.android.tradefed.util.ZipUtil;
48 
49 import java.io.File;
50 import java.io.FileInputStream;
51 import java.io.FileNotFoundException;
52 import java.io.FileOutputStream;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.io.OutputStream;
56 import java.nio.file.Files;
57 import java.nio.file.Path;
58 import java.util.Collection;
59 import java.util.HashMap;
60 import java.util.HashSet;
61 import java.util.LinkedHashMap;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Set;
65 
66 import javax.xml.transform.Transformer;
67 import javax.xml.transform.TransformerException;
68 import javax.xml.transform.TransformerFactory;
69 import javax.xml.transform.stream.StreamResult;
70 import javax.xml.transform.stream.StreamSource;
71 
72 /**
73  * Extension of {@link XmlFormattedGeneratorReporter} and {@link SuiteResultReporter} to handle
74  * Compatibility specific format and operations.
75  */
76 @OptionClass(alias = "result-reporter")
77 public class CertificationSuiteResultReporter extends XmlFormattedGeneratorReporter
78         implements ITestSummaryListener {
79 
80     // The known existing variant of suites.
81     // Adding a new variant requires approval from Android Partner team and Test Harness team.
82     private enum SuiteVariant {
83         CTS_ON_GSI("CTS_ON_GSI", "cts-on-gsi");
84 
85         private final String mReportDisplayName;
86         private final String mConfigName;
87 
SuiteVariant(String reportName, String configName)88         private SuiteVariant(String reportName, String configName) {
89             mReportDisplayName = reportName;
90             mConfigName = configName;
91         }
92 
getReportDisplayName()93         public String getReportDisplayName() {
94             return mReportDisplayName;
95         }
96 
getConfigName()97         public String getConfigName() {
98             return mConfigName;
99         }
100     }
101 
102     public static final String LATEST_LINK_NAME = "latest";
103     public static final String SUMMARY_FILE = "invocation_summary.txt";
104     public static final String HTLM_REPORT_NAME = "test_result.html";
105     public static final String REPORT_XSL_FILE_NAME = "compatibility_result.xsl";
106     public static final String FAILURE_REPORT_NAME = "test_result_failures_suite.html";
107     public static final String FAILURE_XSL_FILE_NAME = "compatibility_failures.xsl";
108 
109     public static final String BUILD_FINGERPRINT = "build_fingerprint";
110 
111     @Option(name = "result-server", description = "Server to publish test results.")
112     private String mResultServer;
113 
114     @Option(
115             name = "disable-result-posting",
116             description ="Disable result posting into report server."
117     )
118     private boolean mDisableResultPosting = false;
119 
120     @Option(name = "include-test-log-tags", description = "Include test log tags in report.")
121     private boolean mIncludeTestLogTags = false;
122 
123     @Option(name = "use-log-saver", description = "Also saves generated result with log saver")
124     private boolean mUseLogSaver = false;
125 
126     @Option(name = "compress-logs", description = "Whether logs will be saved with compression")
127     private boolean mCompressLogs = true;
128 
129     public static final String INCLUDE_HTML_IN_ZIP = "html-in-zip";
130     @Option(name = INCLUDE_HTML_IN_ZIP,
131             description = "Whether failure summary report is included in the zip fie.")
132     private boolean mIncludeHtml = false;
133 
134     @Option(
135             name = "result-attribute",
136             description =
137                     "Extra key-value pairs to be added as attributes and corresponding values "
138                             + "of the \"Result\" tag in the result XML.")
139     private Map<String, String> mResultAttributes = new HashMap<String, String>();
140 
141     // Should be removed for the S release.
142     @Option(
143             name = "cts-on-gsi-variant",
144             description =
145                     "Workaround for the R release to ensure the CTS-on-GSI report can be parsed "
146                             + "by the APFE.")
147     private boolean mCtsOnGsiVariant = false;
148 
149     private CompatibilityBuildHelper mBuildHelper;
150 
151     /** The directory containing the results */
152     private File mResultDir = null;
153     /** The directory containing the logs */
154     private File mLogDir = null;
155 
156     private ResultUploader mUploader;
157 
158     /** LogFileSaver to copy the file to the CTS results folder */
159     private LogFileSaver mTestLogSaver;
160 
161     private Map<LogFile, InputStreamSource> mPreInvocationLogs = new HashMap<>();
162     /** Invocation level Log saver to receive when files are logged */
163     private ILogSaver mLogSaver;
164 
165     private String mReferenceUrl;
166 
167     private Map<String, String> mLoggedFiles;
168 
169     private static final String[] RESULT_RESOURCES = {
170         "compatibility_result.css",
171         "compatibility_result.xsl",
172         "logo.png"
173     };
174 
CertificationSuiteResultReporter()175     public CertificationSuiteResultReporter() {
176         super();
177         mLoggedFiles = new LinkedHashMap<>();
178     }
179 
180     /**
181      * {@inheritDoc}
182      */
183     @Override
invocationStarted(IInvocationContext context)184     public final void invocationStarted(IInvocationContext context) {
185         super.invocationStarted(context);
186 
187         if (mBuildHelper == null) {
188             mBuildHelper = createBuildHelper();
189         }
190         if (mResultDir == null) {
191             initializeResultDirectories();
192         }
193     }
194 
195     @VisibleForTesting
createBuildHelper()196     CompatibilityBuildHelper createBuildHelper() {
197         return new CompatibilityBuildHelper(getPrimaryBuildInfo());
198     }
199 
200     /**
201      * {@inheritDoc}
202      */
203     @Override
testLog(String name, LogDataType type, InputStreamSource stream)204     public void testLog(String name, LogDataType type, InputStreamSource stream) {
205         if (name.endsWith(DeviceInfo.FILE_SUFFIX)) {
206             // Handle device info file case
207             testLogDeviceInfo(name, stream);
208             return;
209         }
210         if (mTestLogSaver == null) {
211             LogFile info = new LogFile(name, null, type);
212             mPreInvocationLogs.put(
213                     info, new SnapshotInputStreamSource(name, stream.createInputStream()));
214             return;
215         }
216         try {
217             File logFile = null;
218             if (mCompressLogs) {
219                 try (InputStream inputStream = stream.createInputStream()) {
220                     logFile = mTestLogSaver.saveAndGZipLogData(name, type, inputStream);
221                 }
222             } else {
223                 try (InputStream inputStream = stream.createInputStream()) {
224                     logFile = mTestLogSaver.saveLogData(name, type, inputStream);
225                 }
226             }
227             CLog.d("Saved logs for %s in %s", name, logFile.getAbsolutePath());
228         } catch (IOException e) {
229             CLog.e("Failed to write log for %s", name);
230             CLog.e(e);
231         }
232     }
233 
234     /** Write device-info files to the result */
testLogDeviceInfo(String name, InputStreamSource stream)235     private void testLogDeviceInfo(String name, InputStreamSource stream) {
236         try {
237             File ediDir = new File(mResultDir, DeviceInfo.RESULT_DIR_NAME);
238             ediDir.mkdirs();
239             File ediFile = new File(ediDir, name);
240             if (!ediFile.exists()) {
241                 // only write this file to the results if not already present
242                 FileUtil.writeToFile(stream.createInputStream(), ediFile);
243             }
244         } catch (IOException e) {
245             CLog.w("Failed to write device info %s to result", name);
246             CLog.e(e);
247         }
248     }
249 
250     /**
251      * {@inheritDoc}
252      */
253     @Override
testLogSaved(String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile)254     public void testLogSaved(String dataName, LogDataType dataType, InputStreamSource dataStream,
255             LogFile logFile) {
256         if (mIncludeTestLogTags) {
257             switch (dataType) {
258                 case BUGREPORT:
259                 case LOGCAT:
260                 case PNG:
261                     mLoggedFiles.put(dataName, logFile.getUrl());
262                     break;
263                 default:
264                     // Do nothing
265                     break;
266             }
267         }
268     }
269 
270     /**
271      * {@inheritDoc}
272      */
273     @Override
putSummary(List<TestSummary> summaries)274     public void putSummary(List<TestSummary> summaries) {
275         for (TestSummary summary : summaries) {
276             if (mReferenceUrl == null && summary.getSummary().getString() != null) {
277                 mReferenceUrl = summary.getSummary().getString();
278             }
279         }
280     }
281 
282     /**
283      * {@inheritDoc}
284      */
285     @Override
setLogSaver(ILogSaver saver)286     public void setLogSaver(ILogSaver saver) {
287         mLogSaver = saver;
288     }
289 
290     /**
291      * Create directory structure where results and logs will be written.
292      */
initializeResultDirectories()293     private void initializeResultDirectories() {
294         CLog.d("Initializing result directory");
295         try {
296             mResultDir = mBuildHelper.getResultDir();
297             if (mResultDir != null) {
298                 mResultDir.mkdirs();
299             }
300         } catch (FileNotFoundException e) {
301             throw new RuntimeException(e);
302         }
303 
304         if (mResultDir == null) {
305             throw new RuntimeException("Result Directory was not created");
306         }
307         if (!mResultDir.exists()) {
308             throw new RuntimeException("Result Directory was not created: " +
309                     mResultDir.getAbsolutePath());
310         }
311 
312         CLog.d("Results Directory: %s", mResultDir.getAbsolutePath());
313 
314         mUploader = new ResultUploader(mResultServer, mBuildHelper.getSuiteName());
315         try {
316             mLogDir = mBuildHelper.getInvocationLogDir();
317         } catch (FileNotFoundException e) {
318             CLog.e(e);
319         }
320         if (mLogDir != null && mLogDir.mkdirs()) {
321             CLog.d("Created log dir %s", mLogDir.getAbsolutePath());
322         }
323         if (mLogDir == null || !mLogDir.exists()) {
324             throw new IllegalArgumentException(String.format("Could not create log dir %s",
325                     mLogDir.getAbsolutePath()));
326         }
327         // During sharding, we reach here before invocationStarted is called so the log_saver will
328         // be null at that point.
329         if (mTestLogSaver == null) {
330             mTestLogSaver = new LogFileSaver(mLogDir);
331             // Log all the early logs from before init.
332             for (LogFile earlyLog : mPreInvocationLogs.keySet()) {
333                 try (InputStreamSource source = mPreInvocationLogs.get(earlyLog)) {
334                     testLog(earlyLog.getPath(), earlyLog.getType(), source);
335                 }
336             }
337             mPreInvocationLogs.clear();
338         }
339     }
340 
341     @Override
createFormatter()342     public IFormatterGenerator createFormatter() {
343         return new CertificationResultXml(
344                 createSuiteName(mBuildHelper.getSuiteName()),
345                 mBuildHelper.getSuiteVersion(),
346                 createSuiteVariant(),
347                 mBuildHelper.getSuitePlan(),
348                 mBuildHelper.getSuiteBuild(),
349                 mReferenceUrl,
350                 getLogUrl(),
351                 mResultAttributes);
352     }
353 
354     @Override
preFormattingSetup(IFormatterGenerator formater)355     public void preFormattingSetup(IFormatterGenerator formater) {
356         super.preFormattingSetup(formater);
357         // Log the summary
358         TestSummary summary = getSummary();
359         try {
360             File summaryFile = new File(mResultDir, SUMMARY_FILE);
361             FileUtil.writeToFile(summary.getSummary().toString(), summaryFile);
362         } catch (IOException e) {
363             CLog.e("Failed to save the summary.");
364             CLog.e(e);
365         }
366 
367         copyDynamicConfigFiles();
368         copyFormattingFiles(mResultDir, mBuildHelper.getSuiteName());
369     }
370 
371     @Override
createResultDir()372     public File createResultDir() throws IOException {
373         return mResultDir;
374     }
375 
376     @Override
postFormattingStep(File resultDir, File reportFile)377     public void postFormattingStep(File resultDir, File reportFile) {
378         super.postFormattingStep(resultDir,reportFile);
379 
380         createChecksum(
381                 resultDir,
382                 getMergedTestRunResults(),
383                 getPrimaryBuildInfo().getBuildAttributes().get(BUILD_FINGERPRINT));
384 
385         File report = null;
386         File failureReport = null;
387         if (mIncludeHtml) {
388             // Create the html reports before the zip file.
389             report = createReport(reportFile);
390             failureReport = createFailureReport(reportFile);
391         }
392         File zippedResults = zipResults(mResultDir);
393         if (!mIncludeHtml) {
394             // Create html reports after zip file so extra data is not uploaded
395             report = createReport(reportFile);
396             failureReport = createFailureReport(reportFile);
397         }
398         if (report != null) {
399             CLog.i("Viewable report: %s", report.getAbsolutePath());
400         }
401         try {
402             if (failureReport.exists()) {
403                 CLog.i("Test Result: %s", failureReport.getCanonicalPath());
404             } else {
405                 CLog.i("Test Result: %s", reportFile.getCanonicalPath());
406             }
407             Path latestLink = createLatestLinkDirectory(mResultDir.toPath());
408             if (latestLink != null) {
409                 CLog.i("Latest results link: " + latestLink.toAbsolutePath());
410             }
411 
412             latestLink = createLatestLinkDirectory(mLogDir.toPath());
413             if (latestLink != null) {
414                 CLog.i("Latest logs link: " + latestLink.toAbsolutePath());
415             }
416 
417             saveLog(reportFile, zippedResults);
418         } catch (IOException e) {
419             CLog.e("Error when handling the post processing of results file:");
420             CLog.e(e);
421         }
422 
423         uploadResult(reportFile);
424     }
425 
426     /**
427      * Return the path in which log saver persists log files or null if
428      * logSaver is not enabled.
429      */
getLogUrl()430     private String getLogUrl() {
431         if (!mUseLogSaver || mLogSaver == null) {
432             return null;
433         }
434 
435         return mLogSaver.getLogReportDir().getUrl();
436     }
437 
438     /**
439      * Update the "latest" symlink to the newest result directory. CTS specific.
440      */
createLatestLinkDirectory(Path directory)441     private Path createLatestLinkDirectory(Path directory) {
442         Path link = null;
443 
444         Path parent = directory.getParent();
445 
446         if (parent != null) {
447             link = parent.resolve(LATEST_LINK_NAME);
448             try {
449                 // if latest already exists, we have to remove it before creating
450                 Files.deleteIfExists(link);
451                 Files.createSymbolicLink(link, directory);
452             } catch (IOException ioe) {
453                 CLog.e("Exception while attempting to create 'latest' link to: [%s]",
454                     directory);
455                 CLog.e(ioe);
456                 return null;
457             } catch (UnsupportedOperationException uoe) {
458                 CLog.e("Failed to create 'latest' symbolic link - unsupported operation");
459                 return null;
460             }
461         }
462         return link;
463     }
464 
465     /**
466      * move the dynamic config files to the results directory
467      */
copyDynamicConfigFiles()468     private void copyDynamicConfigFiles() {
469         File configDir = new File(mResultDir, "config");
470         if (!configDir.exists() && !configDir.mkdir()) {
471             CLog.w(
472                     "Failed to make dynamic config directory \"%s\" in the result.",
473                     configDir.getAbsolutePath());
474         }
475 
476         Set<String> uniqueModules = new HashSet<>();
477         // Check each build of the invocation, in case of multi-device invocation.
478         for (IBuildInfo buildInfo : getInvocationContext().getBuildInfos()) {
479             CompatibilityBuildHelper helper = new CompatibilityBuildHelper(buildInfo);
480             Map<String, File> dcFiles = helper.getDynamicConfigFiles();
481             for (String moduleName : dcFiles.keySet()) {
482                 File srcFile = dcFiles.get(moduleName);
483                 if (!uniqueModules.contains(moduleName)) {
484                     // have not seen config for this module yet, copy into result
485                     File destFile = new File(configDir, moduleName + ".dynamic");
486                     if (destFile.exists()) {
487                         continue;
488                     }
489                     try {
490                         FileUtil.copyFile(srcFile, destFile);
491                         uniqueModules.add(moduleName); // Add to uniqueModules if copy succeeds
492                     } catch (IOException e) {
493                         CLog.w("Failure when copying config file \"%s\" to \"%s\" for module %s",
494                                 srcFile.getAbsolutePath(), destFile.getAbsolutePath(), moduleName);
495                         CLog.e(e);
496                     }
497                 }
498                 FileUtil.deleteFile(srcFile);
499             }
500         }
501     }
502 
503     /**
504      * Copy the xml formatting files stored in this jar to the results directory. CTS specific.
505      *
506      * @param resultsDir
507      */
copyFormattingFiles(File resultsDir, String suiteName)508     private void copyFormattingFiles(File resultsDir, String suiteName) {
509         for (String resultFileName : RESULT_RESOURCES) {
510             InputStream configStream = CertificationResultXml.class.getResourceAsStream(
511                     String.format("/report/%s-%s", suiteName, resultFileName));
512             if (configStream == null) {
513                 // If suite specific files are not available, fallback to common.
514                 configStream = CertificationResultXml.class.getResourceAsStream(
515                     String.format("/report/%s", resultFileName));
516             }
517             if (configStream != null) {
518                 File resultFile = new File(resultsDir, resultFileName);
519                 try {
520                     FileUtil.writeToFile(configStream, resultFile);
521                 } catch (IOException e) {
522                     CLog.w("Failed to write %s to file", resultFileName);
523                 }
524             } else {
525                 CLog.w("Failed to load %s from jar", resultFileName);
526             }
527         }
528     }
529 
530     /**
531      * When enabled, save log data using log saver
532      */
saveLog(File resultFile, File zippedResults)533     private void saveLog(File resultFile, File zippedResults) throws IOException {
534         if (!mUseLogSaver) {
535             return;
536         }
537 
538         FileInputStream fis = null;
539         LogFile logFile = null;
540         try {
541             fis = new FileInputStream(resultFile);
542             logFile = mLogSaver.saveLogData("log-result", LogDataType.XML, fis);
543             CLog.d("Result XML URL: %s", logFile.getUrl());
544             logReportFiles(getConfiguration(), resultFile, resultFile.getName(), LogDataType.XML);
545         } catch (IOException ioe) {
546             CLog.e("error saving XML with log saver");
547             CLog.e(ioe);
548         } finally {
549             StreamUtil.close(fis);
550         }
551         // Save the full results folder.
552         if (zippedResults != null) {
553             FileInputStream zipResultStream = null;
554             try {
555                 zipResultStream = new FileInputStream(zippedResults);
556                 logFile = mLogSaver.saveLogData("results", LogDataType.ZIP, zipResultStream);
557                 CLog.d("Result zip URL: %s", logFile.getUrl());
558                 logReportFiles(getConfiguration(), zippedResults, "results", LogDataType.ZIP);
559             } finally {
560                 StreamUtil.close(zipResultStream);
561             }
562         }
563     }
564 
565     /**
566      * Zip the contents of the given results directory. CTS specific.
567      *
568      * @param resultsDir
569      */
zipResults(File resultsDir)570     private static File zipResults(File resultsDir) {
571         File zipResultFile = null;
572         try {
573             // create a file in parent directory, with same name as resultsDir
574             zipResultFile = new File(resultsDir.getParent(), String.format("%s.zip",
575                     resultsDir.getName()));
576             ZipUtil.createZip(resultsDir, zipResultFile);
577         } catch (IOException e) {
578             CLog.w("Failed to create zip for %s", resultsDir.getName());
579         }
580         return zipResultFile;
581     }
582 
583     /**
584      * When enabled, upload the result to a server. CTS specific.
585      */
uploadResult(File resultFile)586     private void uploadResult(File resultFile) {
587         if (mResultServer != null && !mResultServer.trim().isEmpty() && !mDisableResultPosting) {
588             try {
589                 CLog.d("Result Server: %d", mUploader.uploadResult(resultFile, mReferenceUrl));
590             } catch (IOException ioe) {
591                 CLog.e("IOException while uploading result.");
592                 CLog.e(ioe);
593             }
594         }
595     }
596 
597     /** Generate html report. */
createReport(File inputXml)598     private File createReport(File inputXml) {
599         File report = new File(inputXml.getParentFile(), HTLM_REPORT_NAME);
600         try (InputStream xslStream =
601                         new FileInputStream(
602                                 new File(inputXml.getParentFile(), REPORT_XSL_FILE_NAME));
603                 OutputStream outputStream = new FileOutputStream(report)) {
604             Transformer transformer =
605                     TransformerFactory.newInstance().newTransformer(new StreamSource(xslStream));
606             transformer.transform(new StreamSource(inputXml), new StreamResult(outputStream));
607         } catch (IOException | TransformerException ignored) {
608             CLog.e(ignored);
609             FileUtil.deleteFile(report);
610             return null;
611         }
612         return report;
613     }
614 
615     /**
616      * Generate html report listing an failed tests. CTS specific.
617      */
createFailureReport(File inputXml)618     private File createFailureReport(File inputXml) {
619         File failureReport = new File(inputXml.getParentFile(), FAILURE_REPORT_NAME);
620         try (InputStream xslStream = ResultHandler.class.getResourceAsStream(
621                 String.format("/report/%s", FAILURE_XSL_FILE_NAME));
622              OutputStream outputStream = new FileOutputStream(failureReport)) {
623 
624             Transformer transformer = TransformerFactory.newInstance().newTransformer(
625                     new StreamSource(xslStream));
626             transformer.transform(new StreamSource(inputXml), new StreamResult(outputStream));
627         } catch (IOException | TransformerException ignored) {
628             CLog.e(ignored);
629         }
630         return failureReport;
631     }
632 
633     /**
634      * Generates a checksum files based on the results.
635      */
createChecksum(File resultDir, Collection<TestRunResult> results, String buildFingerprint)636     private void createChecksum(File resultDir, Collection<TestRunResult> results,
637             String buildFingerprint) {
638         CertificationChecksumHelper.tryCreateChecksum(resultDir, results, buildFingerprint);
639     }
640 
641     /** Re-log a result file to all reporters so they are aware of it. */
logReportFiles( IConfiguration configuration, File resultFile, String dataName, LogDataType type)642     private void logReportFiles(
643             IConfiguration configuration, File resultFile, String dataName, LogDataType type) {
644         if (configuration == null) {
645             return;
646         }
647         ILogSaver saver = configuration.getLogSaver();
648         List<ITestInvocationListener> listeners = configuration.getTestInvocationListeners();
649         try (FileInputStreamSource source = new FileInputStreamSource(resultFile)) {
650             LogFile loggedFile = null;
651             try (InputStream stream = source.createInputStream()) {
652                 loggedFile = saver.saveLogData(dataName, type, stream);
653             } catch (IOException e) {
654                 CLog.e(e);
655             }
656             for (ITestInvocationListener listener : listeners) {
657                 if (listener.equals(this)) {
658                     // Avoid logging agaisnt itself
659                     continue;
660                 }
661                 listener.testLog(dataName, type, source);
662                 if (loggedFile != null) {
663                     if (listener instanceof ILogSaverListener) {
664                         ((ILogSaverListener) listener).logAssociation(dataName, loggedFile);
665                     }
666                 }
667             }
668         }
669     }
670 
createSuiteName(String originalSuiteName)671     private String createSuiteName(String originalSuiteName) {
672         if (mCtsOnGsiVariant) {
673             String commandLine = getConfiguration().getCommandLine();
674             // SubprocessConfigBuilder is added to support ATS current way of running things.
675             // It won't be needed after the R release.
676             if (commandLine.startsWith("cts-on-gsi")
677                     || commandLine.startsWith(
678                             SubprocessConfigBuilder.createConfigName("cts-on-gsi"))) {
679                 return "VTS";
680             }
681         }
682         return originalSuiteName;
683     }
684 
createSuiteVariant()685     private String createSuiteVariant() {
686         IConfiguration currentConfig = getConfiguration();
687         String commandLine = currentConfig.getCommandLine();
688         for (SuiteVariant var : SuiteVariant.values()) {
689             if (commandLine.startsWith(var.getConfigName() + " ")
690                     || commandLine.equals(var.getConfigName())) {
691                 return var.getReportDisplayName();
692             }
693         }
694         return null;
695     }
696 }
697