• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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