Home | History | Annotate | Download | only in util
      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.tradefed.util;
     18 
     19 import com.android.tradefed.result.MetricsXMLResultReporter;
     20 import com.android.tradefed.result.TestDescription;
     21 import com.android.tradefed.testtype.metricregression.Metrics;
     22 
     23 import com.google.common.annotations.VisibleForTesting;
     24 
     25 import org.xml.sax.Attributes;
     26 import org.xml.sax.SAXException;
     27 import org.xml.sax.helpers.DefaultHandler;
     28 
     29 import java.io.BufferedInputStream;
     30 import java.io.File;
     31 import java.io.FileInputStream;
     32 import java.io.IOException;
     33 import java.io.InputStream;
     34 import java.util.List;
     35 import java.util.Set;
     36 
     37 import javax.xml.parsers.ParserConfigurationException;
     38 import javax.xml.parsers.SAXParser;
     39 import javax.xml.parsers.SAXParserFactory;
     40 
     41 /** Parser that extracts test metrics result data generated by {@link MetricsXMLResultReporter}. */
     42 public class MetricsXmlParser {
     43 
     44     /** Thrown when MetricsXmlParser fails to parse a metrics xml file. */
     45     public static class ParseException extends Exception {
     46         public ParseException(Throwable cause) {
     47             super(cause);
     48         }
     49 
     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> mBlacklistMetrics;
     74 
     75         public MetricsXmlHandler(Metrics metrics, Set<String> blacklistMetrics) {
     76             mMetrics = metrics;
     77             mBlacklistMetrics = blacklistMetrics;
     78         }
     79 
     80         @Override
     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 (!mBlacklistMetrics.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 (!mBlacklistMetrics.contains(metricName)) {
    106                     mMetrics.addTestMetric(mCurrentTest, metricName, metricValue);
    107                 }
    108             }
    109         }
    110 
    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 blacklistMetrics 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      */
    133     public static Metrics parse(
    134             Set<String> blacklistMetrics, 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, blacklistMetrics, 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
    149     public static Metrics parse(Metrics metrics, Set<String> blacklistMetrics, 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, blacklistMetrics));
    156             return metrics;
    157         } catch (ParserConfigurationException | SAXException | IOException e) {
    158             throw new ParseException(e);
    159         }
    160     }
    161 }
    162