blob: 2a689577c9efad7188701e314274f27e01c44638 [file] [log] [blame]
/*
* 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<String> mBlocklistMetrics;
public MetricsXmlHandler(Metrics metrics, Set<String> 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<String> blocklistMetrics, boolean strictMode, List<File> 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<String> 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);
}
}
}