/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.regression.tests; import com.android.tradefed.result.MetricsXMLResultReporter; import com.android.tradefed.result.TestDescription; import com.google.common.annotations.VisibleForTesting; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; /** Parser that extracts test metrics result data generated by {@link MetricsXMLResultReporter}. */ public class MetricsXmlParser { /** Thrown when MetricsXmlParser fails to parse a metrics xml file. */ @SuppressWarnings("serial") public static class ParseException extends Exception { public ParseException(Throwable cause) { super(cause); } public ParseException(String msg, Throwable cause) { super(msg, cause); } } /* * Parses the xml format. Expected tags/attributes are: * testsuite name="runname" tests="X" * runmetric name="metric1" value="1.0" * testcase classname="FooTest" testname="testMethodName" * testmetric name="metric2" value="1.0" */ private static class MetricsXmlHandler extends DefaultHandler { private static final String TESTSUITE_TAG = "testsuite"; private static final String TESTCASE_TAG = "testcase"; private static final String TIME_TAG = "time"; private static final String RUNMETRIC_TAG = "runmetric"; private static final String TESTMETRIC_TAG = "testmetric"; private TestDescription mCurrentTest = null; private Metrics mMetrics; private Set mBlocklistMetrics; public MetricsXmlHandler(Metrics metrics, Set blocklistMetrics) { mMetrics = metrics; mBlocklistMetrics = blocklistMetrics; } @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { if (TESTSUITE_TAG.equalsIgnoreCase(name)) { // top level tag - maps to a test run in TF terminology String testCount = getMandatoryAttribute(name, "tests", attributes); mMetrics.setNumTests(Integer.parseInt(testCount)); mMetrics.addRunMetric(TIME_TAG, getMandatoryAttribute(name, TIME_TAG, attributes)); } if (TESTCASE_TAG.equalsIgnoreCase(name)) { // start of description of an individual test method String testClassName = getMandatoryAttribute(name, "classname", attributes); String methodName = getMandatoryAttribute(name, "testname", attributes); mCurrentTest = new TestDescription(testClassName, methodName); } if (RUNMETRIC_TAG.equalsIgnoreCase(name)) { String metricName = getMandatoryAttribute(name, "name", attributes); String metricValue = getMandatoryAttribute(name, "value", attributes); if (!mBlocklistMetrics.contains(metricName)) { mMetrics.addRunMetric(metricName, metricValue); } } if (TESTMETRIC_TAG.equalsIgnoreCase(name)) { String metricName = getMandatoryAttribute(name, "name", attributes); String metricValue = getMandatoryAttribute(name, "value", attributes); if (!mBlocklistMetrics.contains(metricName)) { mMetrics.addTestMetric(mCurrentTest, metricName, metricValue); } } } private String getMandatoryAttribute(String tagName, String attrName, Attributes attributes) throws SAXException { String value = attributes.getValue(attrName); if (value == null) { throw new SAXException( String.format( "Malformed XML, could not find '%s' attribute in '%s'", attrName, tagName)); } return value; } } /** * Parses xml data contained in given input files. * * @param blocklistMetrics ignore the metrics with these names * @param strictMode whether to throw an exception when metric validation fails * @param metricXmlFiles a list of metric xml files * @return a Metric object containing metrics from all metric files * @throws ParseException if input could not be parsed */ public static Metrics parse( Set blocklistMetrics, boolean strictMode, List metricXmlFiles) throws ParseException { Metrics metrics = new Metrics(strictMode); for (File xml : metricXmlFiles) { try (InputStream is = new BufferedInputStream(new FileInputStream(xml))) { parse(metrics, blocklistMetrics, is); } catch (Exception e) { throw new ParseException("Unable to parse " + xml.getPath(), e); } } metrics.validate(metricXmlFiles.size()); return metrics; } @VisibleForTesting public static Metrics parse(Metrics metrics, Set blocklistMetrics, InputStream is) throws ParseException { try { SAXParserFactory parserFactory = SAXParserFactory.newInstance(); parserFactory.setNamespaceAware(true); SAXParser parser = parserFactory.newSAXParser(); parser.parse(is, new MetricsXmlHandler(metrics, blocklistMetrics)); return metrics; } catch (ParserConfigurationException | SAXException | IOException e) { throw new ParseException(e); } } }