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 17 package com.android.regression.tests; 18 19 import com.android.tradefed.result.MetricsXMLResultReporter; 20 import com.android.tradefed.result.TestDescription; 21 22 import com.google.common.annotations.VisibleForTesting; 23 24 import org.xml.sax.Attributes; 25 import org.xml.sax.SAXException; 26 import org.xml.sax.helpers.DefaultHandler; 27 28 import java.io.BufferedInputStream; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.util.List; 34 import java.util.Set; 35 36 import javax.xml.parsers.ParserConfigurationException; 37 import javax.xml.parsers.SAXParser; 38 import javax.xml.parsers.SAXParserFactory; 39 40 /** Parser that extracts test metrics result data generated by {@link MetricsXMLResultReporter}. */ 41 public class MetricsXmlParser { 42 43 /** Thrown when MetricsXmlParser fails to parse a metrics xml file. */ 44 @SuppressWarnings("serial") 45 public static class ParseException extends Exception { ParseException(Throwable cause)46 public ParseException(Throwable cause) { 47 super(cause); 48 } 49 ParseException(String msg, Throwable cause)50 public ParseException(String msg, Throwable cause) { 51 super(msg, cause); 52 } 53 } 54 55 /* 56 * Parses the xml format. Expected tags/attributes are: 57 * testsuite name="runname" tests="X" 58 * runmetric name="metric1" value="1.0" 59 * testcase classname="FooTest" testname="testMethodName" 60 * testmetric name="metric2" value="1.0" 61 */ 62 private static class MetricsXmlHandler extends DefaultHandler { 63 64 private static final String TESTSUITE_TAG = "testsuite"; 65 private static final String TESTCASE_TAG = "testcase"; 66 private static final String TIME_TAG = "time"; 67 private static final String RUNMETRIC_TAG = "runmetric"; 68 private static final String TESTMETRIC_TAG = "testmetric"; 69 70 private TestDescription mCurrentTest = null; 71 72 private Metrics mMetrics; 73 private Set<String> mBlocklistMetrics; 74 MetricsXmlHandler(Metrics metrics, Set<String> blocklistMetrics)75 public MetricsXmlHandler(Metrics metrics, Set<String> blocklistMetrics) { 76 mMetrics = metrics; 77 mBlocklistMetrics = blocklistMetrics; 78 } 79 80 @Override startElement(String uri, String localName, String name, Attributes attributes)81 public void startElement(String uri, String localName, String name, Attributes attributes) 82 throws SAXException { 83 if (TESTSUITE_TAG.equalsIgnoreCase(name)) { 84 // top level tag - maps to a test run in TF terminology 85 String testCount = getMandatoryAttribute(name, "tests", attributes); 86 mMetrics.setNumTests(Integer.parseInt(testCount)); 87 mMetrics.addRunMetric(TIME_TAG, getMandatoryAttribute(name, TIME_TAG, attributes)); 88 } 89 if (TESTCASE_TAG.equalsIgnoreCase(name)) { 90 // start of description of an individual test method 91 String testClassName = getMandatoryAttribute(name, "classname", attributes); 92 String methodName = getMandatoryAttribute(name, "testname", attributes); 93 mCurrentTest = new TestDescription(testClassName, methodName); 94 } 95 if (RUNMETRIC_TAG.equalsIgnoreCase(name)) { 96 String metricName = getMandatoryAttribute(name, "name", attributes); 97 String metricValue = getMandatoryAttribute(name, "value", attributes); 98 if (!mBlocklistMetrics.contains(metricName)) { 99 mMetrics.addRunMetric(metricName, metricValue); 100 } 101 } 102 if (TESTMETRIC_TAG.equalsIgnoreCase(name)) { 103 String metricName = getMandatoryAttribute(name, "name", attributes); 104 String metricValue = getMandatoryAttribute(name, "value", attributes); 105 if (!mBlocklistMetrics.contains(metricName)) { 106 mMetrics.addTestMetric(mCurrentTest, metricName, metricValue); 107 } 108 } 109 } 110 getMandatoryAttribute(String tagName, String attrName, Attributes attributes)111 private String getMandatoryAttribute(String tagName, String attrName, Attributes attributes) 112 throws SAXException { 113 String value = attributes.getValue(attrName); 114 if (value == null) { 115 throw new SAXException( 116 String.format( 117 "Malformed XML, could not find '%s' attribute in '%s'", 118 attrName, tagName)); 119 } 120 return value; 121 } 122 } 123 124 /** 125 * Parses xml data contained in given input files. 126 * 127 * @param blocklistMetrics ignore the metrics with these names 128 * @param strictMode whether to throw an exception when metric validation fails 129 * @param metricXmlFiles a list of metric xml files 130 * @return a Metric object containing metrics from all metric files 131 * @throws ParseException if input could not be parsed 132 */ parse( Set<String> blocklistMetrics, boolean strictMode, List<File> metricXmlFiles)133 public static Metrics parse( 134 Set<String> blocklistMetrics, boolean strictMode, List<File> metricXmlFiles) 135 throws ParseException { 136 Metrics metrics = new Metrics(strictMode); 137 for (File xml : metricXmlFiles) { 138 try (InputStream is = new BufferedInputStream(new FileInputStream(xml))) { 139 parse(metrics, blocklistMetrics, is); 140 } catch (Exception e) { 141 throw new ParseException("Unable to parse " + xml.getPath(), e); 142 } 143 } 144 metrics.validate(metricXmlFiles.size()); 145 return metrics; 146 } 147 148 @VisibleForTesting parse(Metrics metrics, Set<String> blocklistMetrics, InputStream is)149 public static Metrics parse(Metrics metrics, Set<String> blocklistMetrics, InputStream is) 150 throws ParseException { 151 try { 152 SAXParserFactory parserFactory = SAXParserFactory.newInstance(); 153 parserFactory.setNamespaceAware(true); 154 SAXParser parser = parserFactory.newSAXParser(); 155 parser.parse(is, new MetricsXmlHandler(metrics, blocklistMetrics)); 156 return metrics; 157 } catch (ParserConfigurationException | SAXException | IOException e) { 158 throw new ParseException(e); 159 } 160 } 161 } 162