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