1 /*
2  * Copyright (C) 2015 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.util;
17 
18 import org.xmlpull.v1.XmlPullParser;
19 import org.xmlpull.v1.XmlPullParserException;
20 import org.xmlpull.v1.XmlPullParserFactory;
21 import org.xmlpull.v1.XmlSerializer;
22 
23 import java.io.File;
24 import java.io.FileNotFoundException;
25 import java.io.FileOutputStream;
26 import java.io.FileReader;
27 import java.io.IOException;
28 import java.io.OutputStream;
29 import java.net.InetAddress;
30 import java.net.UnknownHostException;
31 import java.text.SimpleDateFormat;
32 import java.util.ArrayList;
33 import java.util.Comparator;
34 import java.util.Collections;
35 import java.util.Date;
36 import java.util.List;
37 import java.util.Map.Entry;
38 import java.util.Set;
39 
40 /**
41  * Handles conversion of results to/from files.
42  */
43 public class ResultHandler {
44 
45     private static final String ENCODING = "UTF-8";
46     private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
47     private static final String NS = null;
48     private static final String RESULT_FILE_VERSION = "5.0";
49     /* package */ static final String TEST_RESULT_FILE_NAME = "test_result.xml";
50 
51     // XML constants
52     private static final String ABI_ATTR = "abi";
53     private static final String BUGREPORT_TAG = "BugReport";
54     private static final String BUILD_FINGERPRINT = "build_fingerprint";
55     private static final String BUILD_ID = "build_id";
56     private static final String BUILD_PRODUCT = "build_product";
57     private static final String BUILD_TAG = "Build";
58     private static final String CASE_TAG = "TestCase";
59     private static final String COMMAND_LINE_ARGS = "command_line_args";
60     private static final String DEVICES_ATTR = "devices";
61     private static final String DONE_ATTR = "done";
62     private static final String END_DISPLAY_TIME_ATTR = "end_display";
63     private static final String END_TIME_ATTR = "end";
64     private static final String FAILED_ATTR = "failed";
65     private static final String FAILURE_TAG = "Failure";
66     private static final String HOST_NAME_ATTR = "host_name";
67     private static final String JAVA_VENDOR_ATTR = "java_vendor";
68     private static final String JAVA_VERSION_ATTR = "java_version";
69     private static final String LOGCAT_TAG = "Logcat";
70     private static final String LOG_URL_ATTR = "log_url";
71     private static final String MESSAGE_ATTR = "message";
72     private static final String MODULE_TAG = "Module";
73     private static final String MODULES_EXECUTED_ATTR = "modules_done";
74     private static final String MODULES_TOTAL_ATTR = "modules_total";
75     private static final String NAME_ATTR = "name";
76     private static final String NOT_EXECUTED_ATTR = "not_executed";
77     private static final String OS_ARCH_ATTR = "os_arch";
78     private static final String OS_NAME_ATTR = "os_name";
79     private static final String OS_VERSION_ATTR = "os_version";
80     private static final String PASS_ATTR = "pass";
81     private static final String REPORT_VERSION_ATTR = "report_version";
82     private static final String REFERENCE_URL_ATTR = "reference_url";
83     private static final String RESULT_ATTR = "result";
84     private static final String RESULT_TAG = "Result";
85     private static final String RUNTIME_ATTR = "runtime";
86     private static final String SCREENSHOT_TAG = "Screenshot";
87     private static final String STACK_TAG = "StackTrace";
88     private static final String START_DISPLAY_TIME_ATTR = "start_display";
89     private static final String START_TIME_ATTR = "start";
90     private static final String SUITE_NAME_ATTR = "suite_name";
91     private static final String SUITE_PLAN_ATTR = "suite_plan";
92     private static final String SUITE_VERSION_ATTR = "suite_version";
93     private static final String SUITE_BUILD_ATTR = "suite_build_number";
94     private static final String SUMMARY_TAG = "Summary";
95     private static final String TEST_TAG = "Test";
96 
97     /**
98      * @param resultsDir
99      */
getResults(File resultsDir)100     public static List<IInvocationResult> getResults(File resultsDir) {
101         List<IInvocationResult> results = new ArrayList<>();
102         File[] files = resultsDir.listFiles();
103         if (files == null || files.length == 0) {
104             // No results, just return the empty list
105             return results;
106         }
107         for (File resultDir : files) {
108             if (!resultDir.isDirectory()) {
109                 continue;
110             }
111             try {
112                 File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
113                 if (!resultFile.exists()) {
114                     continue;
115                 }
116                 IInvocationResult invocation = new InvocationResult();
117                 XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
118                 XmlPullParser parser = factory.newPullParser();
119                 parser.setInput(new FileReader(resultFile));
120 
121                 parser.nextTag();
122                 parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG);
123                 invocation.setStartTime(Long.valueOf(
124                         parser.getAttributeValue(NS, START_TIME_ATTR)));
125                 invocation.setTestPlan(parser.getAttributeValue(NS, SUITE_PLAN_ATTR));
126                 invocation.setCommandLineArgs(parser.getAttributeValue(NS, COMMAND_LINE_ARGS));
127                 String deviceList = parser.getAttributeValue(NS, DEVICES_ATTR);
128                 for (String device : deviceList.split(",")) {
129                     invocation.addDeviceSerial(device);
130                 }
131 
132                 parser.nextTag();
133                 parser.require(XmlPullParser.START_TAG, NS, BUILD_TAG);
134                 invocation.addInvocationInfo(BUILD_ID, parser.getAttributeValue(NS, BUILD_ID));
135                 invocation.addInvocationInfo(BUILD_PRODUCT, parser.getAttributeValue(NS,
136                         BUILD_PRODUCT));
137                 invocation.setBuildFingerprint(parser.getAttributeValue(NS, BUILD_FINGERPRINT));
138 
139                 // TODO(stuartscott): may want to reload these incase the retry was done with
140                 // --skip-device-info flag
141                 parser.nextTag();
142                 parser.require(XmlPullParser.END_TAG, NS, BUILD_TAG);
143                 parser.nextTag();
144                 parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG);
145                 parser.nextTag();
146                 parser.require(XmlPullParser.END_TAG, NS, SUMMARY_TAG);
147                 while (parser.nextTag() == XmlPullParser.START_TAG) {
148                     parser.require(XmlPullParser.START_TAG, NS, MODULE_TAG);
149                     String name = parser.getAttributeValue(NS, NAME_ATTR);
150                     String abi = parser.getAttributeValue(NS, ABI_ATTR);
151                     String moduleId = AbiUtils.createId(abi, name);
152                     boolean done = Boolean.parseBoolean(parser.getAttributeValue(NS, DONE_ATTR));
153                     IModuleResult module = invocation.getOrCreateModule(moduleId);
154                     module.setDone(done);
155                     while (parser.nextTag() == XmlPullParser.START_TAG) {
156                         parser.require(XmlPullParser.START_TAG, NS, CASE_TAG);
157                         String caseName = parser.getAttributeValue(NS, NAME_ATTR);
158                         ICaseResult testCase = module.getOrCreateResult(caseName);
159                         while (parser.nextTag() == XmlPullParser.START_TAG) {
160                             parser.require(XmlPullParser.START_TAG, NS, TEST_TAG);
161                             String testName = parser.getAttributeValue(NS, NAME_ATTR);
162                             ITestResult test = testCase.getOrCreateResult(testName);
163                             String result = parser.getAttributeValue(NS, RESULT_ATTR);
164                             test.setResultStatus(TestStatus.getStatus(result));
165                             if (parser.nextTag() == XmlPullParser.START_TAG) {
166                                 if (parser.getName().equals(FAILURE_TAG)) {
167                                     test.setMessage(parser.getAttributeValue(NS, MESSAGE_ATTR));
168                                     if (parser.nextTag() == XmlPullParser.START_TAG) {
169                                         parser.require(XmlPullParser.START_TAG, NS, STACK_TAG);
170                                         test.setStackTrace(parser.nextText());
171                                         parser.require(XmlPullParser.END_TAG, NS, STACK_TAG);
172                                         parser.nextTag();
173                                     }
174                                     parser.require(XmlPullParser.END_TAG, NS, FAILURE_TAG);
175                                     parser.nextTag();
176                                 } else if (parser.getName().equals(BUGREPORT_TAG)) {
177                                     test.setBugReport(parser.nextText());
178                                     parser.nextTag();
179                                 } else if (parser.getName().equals(LOGCAT_TAG)) {
180                                     test.setLog(parser.nextText());
181                                     parser.nextTag();
182                                 } else if (parser.getName().equals(SCREENSHOT_TAG)) {
183                                     test.setScreenshot(parser.nextText());
184                                     parser.nextTag();
185                                 } else {
186                                     test.setReportLog(ReportLog.parse(parser));
187                                     parser.nextTag();
188                                 }
189                             }
190                             parser.require(XmlPullParser.END_TAG, NS, TEST_TAG);
191                         }
192                         parser.require(XmlPullParser.END_TAG, NS, CASE_TAG);
193                     }
194                     parser.require(XmlPullParser.END_TAG, NS, MODULE_TAG);
195                 }
196                 parser.require(XmlPullParser.END_TAG, NS, RESULT_TAG);
197                 results.add(invocation);
198             } catch (XmlPullParserException e) {
199                 e.printStackTrace();
200             } catch (FileNotFoundException e) {
201                 e.printStackTrace();
202             } catch (IOException e) {
203                 e.printStackTrace();
204             }
205         }
206         // Sort the table entries on each entry's timestamp.
207         Collections.sort(results, new Comparator<IInvocationResult>() {
208             public int compare(IInvocationResult result1, IInvocationResult result2) {
209                 return Long.compare(result1.getStartTime(), result2.getStartTime());
210             }
211         });
212         return results;
213     }
214 
215     /**
216      * @param result
217      * @param resultDir
218      * @param startTime
219      * @param referenceUrl A nullable string that can contain a URL to a related data
220      * @param logUrl A nullable string that can contain a URL to related log files
221      * @param commandLineArgs A string containing the arguments to the run command
222      * @return The result file created.
223      * @throws IOException
224      * @throws XmlPullParserException
225      */
writeResults(String suiteName, String suiteVersion, String suitePlan, String suiteBuild, IInvocationResult result, File resultDir, long startTime, long endTime, String referenceUrl, String logUrl, String commandLineArgs)226     public static File writeResults(String suiteName, String suiteVersion, String suitePlan,
227             String suiteBuild, IInvocationResult result, File resultDir,
228             long startTime, long endTime, String referenceUrl, String logUrl,
229             String commandLineArgs)
230                     throws IOException, XmlPullParserException {
231         int passed = result.countResults(TestStatus.PASS);
232         int failed = result.countResults(TestStatus.FAIL);
233         int notExecuted = result.countResults(TestStatus.NOT_EXECUTED);
234         File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
235         OutputStream stream = new FileOutputStream(resultFile);
236         XmlSerializer serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer();
237         serializer.setOutput(stream, ENCODING);
238         serializer.startDocument(ENCODING, false);
239         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
240         serializer.processingInstruction(
241                 "xml-stylesheet type=\"text/xsl\" href=\"compatibility_result.xsl\"");
242         serializer.startTag(NS, RESULT_TAG);
243         serializer.attribute(NS, START_TIME_ATTR, String.valueOf(startTime));
244         serializer.attribute(NS, END_TIME_ATTR, String.valueOf(endTime));
245         serializer.attribute(NS, START_DISPLAY_TIME_ATTR, toReadableDateString(startTime));
246         serializer.attribute(NS, END_DISPLAY_TIME_ATTR, toReadableDateString(endTime));
247 
248         serializer.attribute(NS, SUITE_NAME_ATTR, suiteName);
249         serializer.attribute(NS, SUITE_VERSION_ATTR, suiteVersion);
250         serializer.attribute(NS, SUITE_PLAN_ATTR, suitePlan);
251         serializer.attribute(NS, SUITE_BUILD_ATTR, suiteBuild);
252         serializer.attribute(NS, REPORT_VERSION_ATTR, RESULT_FILE_VERSION);
253         serializer.attribute(NS, COMMAND_LINE_ARGS, nullToEmpty(commandLineArgs));
254 
255         if (referenceUrl != null) {
256             serializer.attribute(NS, REFERENCE_URL_ATTR, referenceUrl);
257         }
258 
259         if (logUrl != null) {
260             serializer.attribute(NS, LOG_URL_ATTR, logUrl);
261         }
262 
263         // Device Info
264         Set<String> devices = result.getDeviceSerials();
265         StringBuilder deviceList = new StringBuilder();
266         boolean first = true;
267         for (String device : devices) {
268             if (first) {
269                 first = false;
270             } else {
271                 deviceList.append(",");
272             }
273             deviceList.append(device);
274         }
275         serializer.attribute(NS, DEVICES_ATTR, deviceList.toString());
276 
277         // Host Info
278         String hostName = "";
279         try {
280             hostName = InetAddress.getLocalHost().getHostName();
281         } catch (UnknownHostException ignored) {}
282         serializer.attribute(NS, HOST_NAME_ATTR, hostName);
283         serializer.attribute(NS, OS_NAME_ATTR, System.getProperty("os.name"));
284         serializer.attribute(NS, OS_VERSION_ATTR, System.getProperty("os.version"));
285         serializer.attribute(NS, OS_ARCH_ATTR, System.getProperty("os.arch"));
286         serializer.attribute(NS, JAVA_VENDOR_ATTR, System.getProperty("java.vendor"));
287         serializer.attribute(NS, JAVA_VERSION_ATTR, System.getProperty("java.version"));
288 
289         // Build Info
290         serializer.startTag(NS, BUILD_TAG);
291         for (Entry<String, String> entry : result.getInvocationInfo().entrySet()) {
292             serializer.attribute(NS, entry.getKey(), entry.getValue());
293         }
294         serializer.endTag(NS, BUILD_TAG);
295 
296         // Summary
297         serializer.startTag(NS, SUMMARY_TAG);
298         serializer.attribute(NS, PASS_ATTR, Integer.toString(passed));
299         serializer.attribute(NS, FAILED_ATTR, Integer.toString(failed));
300         serializer.attribute(NS, NOT_EXECUTED_ATTR, Integer.toString(notExecuted));
301         serializer.attribute(NS, MODULES_EXECUTED_ATTR,
302                 Integer.toString(result.getModuleCompleteCount()));
303         serializer.attribute(NS, MODULES_TOTAL_ATTR,
304                 Integer.toString(result.getModules().size()));
305         serializer.endTag(NS, SUMMARY_TAG);
306 
307         // Results
308         for (IModuleResult module : result.getModules()) {
309             serializer.startTag(NS, MODULE_TAG);
310             serializer.attribute(NS, NAME_ATTR, module.getName());
311             serializer.attribute(NS, ABI_ATTR, module.getAbi());
312             serializer.attribute(NS, RUNTIME_ATTR, String.valueOf(module.getRuntime()));
313             serializer.attribute(NS, DONE_ATTR, Boolean.toString(module.isDone()));
314             for (ICaseResult cr : module.getResults()) {
315                 serializer.startTag(NS, CASE_TAG);
316                 serializer.attribute(NS, NAME_ATTR, cr.getName());
317                 for (ITestResult r : cr.getResults()) {
318                     serializer.startTag(NS, TEST_TAG);
319                     serializer.attribute(NS, RESULT_ATTR, r.getResultStatus().getValue());
320                     serializer.attribute(NS, NAME_ATTR, r.getName());
321                     String message = r.getMessage();
322                     if (message != null) {
323                         serializer.startTag(NS, FAILURE_TAG);
324                         serializer.attribute(NS, MESSAGE_ATTR, message);
325                         String stackTrace = r.getStackTrace();
326                         if (stackTrace != null) {
327                             serializer.startTag(NS, STACK_TAG);
328                             serializer.text(stackTrace);
329                             serializer.endTag(NS, STACK_TAG);
330                         }
331                         serializer.endTag(NS, FAILURE_TAG);
332                     }
333                     String bugreport = r.getBugReport();
334                     if (bugreport != null) {
335                         serializer.startTag(NS, BUGREPORT_TAG);
336                         serializer.text(bugreport);
337                         serializer.endTag(NS, BUGREPORT_TAG);
338                     }
339                     String logcat = r.getLog();
340                     if (logcat != null) {
341                         serializer.startTag(NS, LOGCAT_TAG);
342                         serializer.text(logcat);
343                         serializer.endTag(NS, LOGCAT_TAG);
344                     }
345                     String screenshot = r.getScreenshot();
346                     if (screenshot != null) {
347                         serializer.startTag(NS, SCREENSHOT_TAG);
348                         serializer.text(screenshot);
349                         serializer.endTag(NS, SCREENSHOT_TAG);
350                     }
351                     ReportLog report = r.getReportLog();
352                     if (report != null) {
353                         ReportLog.serialize(serializer, report);
354                     }
355                     serializer.endTag(NS, TEST_TAG);
356                 }
357                 serializer.endTag(NS, CASE_TAG);
358             }
359             serializer.endTag(NS, MODULE_TAG);
360         }
361         serializer.endDocument();
362         return resultFile;
363     }
364 
365     /**
366      * Find the IInvocationResult for the given sessionId.
367      */
findResult(File resultsDir, Integer sessionId)368     public static IInvocationResult findResult(File resultsDir, Integer sessionId)
369             throws FileNotFoundException {
370         if (sessionId < 0) {
371             throw new IllegalArgumentException(
372                 String.format("Invalid session id [%d] ", sessionId));
373         }
374 
375         List<IInvocationResult> results = getResults(resultsDir);
376         if (results == null || sessionId >= results.size()) {
377             throw new RuntimeException(String.format("Could not find session [%d]", sessionId));
378         }
379         return results.get(sessionId);
380     }
381 
382     /**
383      * Return the given time as a {@link String} suitable for displaying.
384      * <p/>
385      * Example: Fri Aug 20 15:13:03 PDT 2010
386      *
387      * @param time the epoch time in ms since midnight Jan 1, 1970
388      */
toReadableDateString(long time)389     static String toReadableDateString(long time) {
390         SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
391         return dateFormat.format(new Date(time));
392     }
393 
394     /**
395      * When nullable is null, return an empty string. Otherwise, return the value in nullable.
396      */
nullToEmpty(String nullable)397     private static String nullToEmpty(String nullable) {
398         return nullable == null ? "" : nullable;
399     }
400 }
401